File: WebAuthenticator\WebAuthenticator.shared.cs
Web Access
Project: src\src\Essentials\src\Essentials.csproj (Microsoft.Maui.Essentials)
#nullable enable
using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Maui.ApplicationModel;
 
namespace Microsoft.Maui.Authentication
{
	/// <summary>
	/// A web navigation API intended to be used for authentication with external web services such as OAuth.
	/// </summary>
	public interface IWebAuthenticator
	{
		/// <summary>
		/// Begin an authentication flow by navigating to the specified URL and waiting for a callback/redirect to the callback URL scheme.
		/// </summary>
		/// <param name="webAuthenticatorOptions">A <see cref="WebAuthenticatorOptions"/> instance containing additional configuration for this authentication call.</param>
		/// <returns>A <see cref="WebAuthenticatorResult"/> object with the results of this operation.</returns>
		/// <exception cref="TaskCanceledException">Thrown when the user canceled the authentication flow.</exception>
		/// <exception cref="PlatformNotSupportedException">Windows: Thrown when called on Windows.</exception>
		/// <exception cref="FeatureNotSupportedException">iOS/macOS: Thrown when iOS version is less than 13 is used or macOS less than 13.1 is used.</exception>
		/// <exception cref="InvalidOperationException">
		/// <para>Android: Thrown when the no IntentFilter has been created for the callback URL.</para>
		/// </exception>
		Task<WebAuthenticatorResult> AuthenticateAsync(WebAuthenticatorOptions webAuthenticatorOptions);
	}
 
	/// <summary>
	/// Provides abstractions for the platform web authenticator callbacks triggered when using <see cref="WebAuthenticator"/>.
	/// </summary>
	public interface IPlatformWebAuthenticatorCallback
	{
#if IOS || MACCATALYST || MACOS
		/// <summary>
		/// Opens the specified URI to start the authentication flow.
		/// </summary>
		/// <param name="uri">The URI to open that will start the authentication flow.</param>
		/// <returns><see langword="true"/> when the URI has been opened, otherwise <see langword="false"/>.</returns>
		bool OpenUrlCallback(Uri uri);
#elif ANDROID
		/// <summary>
		/// The event that is triggered when an authentication flow calls back into the Android application.
		/// </summary>
		/// <param name="intent">An <see cref="Android.Content.Intent"/> object containing additional data about this resume operation.</param>
		/// <returns><see langword="true"/> when the callback can be processed, otherwise <see langword="false"/>.</returns>
		bool OnResumeCallback(Android.Content.Intent intent);
#endif
	}
 
	/// <summary>
	/// Provides abstractions used for decoding a URI returned from a authentication request, for use with <see cref="IWebAuthenticator"/>.
	/// </summary>
	public interface IWebAuthenticatorResponseDecoder
	{
		/// <summary>
		/// Decodes the given URIs query string into a dictionary.
		/// </summary>
		/// <param name="uri">The <see cref="Uri"/> object to decode the query parameters from.</param>
		/// <returns>A <see cref="IDictionary{TKey, TValue}"/> object where each of the query parameters values of <paramref name="uri"/> are accessible through their respective keys.</returns>
		IDictionary<string, string>? DecodeResponse(Uri uri);
	}
 
	/// <summary>
	/// A web navigation API intended to be used for Authentication with external web services such as OAuth.
	/// </summary>
	/// <remarks>
	/// This API helps with navigating to a start URL and waiting for a callback URL to the app.  Your app must 
	/// be registered to handle the callback scheme you provide in the call to authenticate.
	/// </remarks>
	public static class WebAuthenticator
	{
		/// <summary>Begin an authentication flow by navigating to the specified url and waiting for a callback/redirect to the callbackUrl scheme.</summary>
		/// <param name="url"> Url to navigate to, beginning the authentication flow.</param>
		/// <param name="callbackUrl"> Expected callback url that the navigation flow will eventually redirect to.</param>
		/// <returns>Returns a result parsed out from the callback url.</returns>
#if !NETSTANDARD
		[System.Runtime.Versioning.UnsupportedOSPlatform("windows")]
#endif
		public static Task<WebAuthenticatorResult> AuthenticateAsync(Uri url, Uri callbackUrl)
			=> Current.AuthenticateAsync(url, callbackUrl);
 
		/// <summary>Begin an authentication flow by navigating to the specified url and waiting for a callback/redirect to the callbackUrl scheme.The start url and callbackUrl are specified in the webAuthenticatorOptions.</summary>
		/// <param name="webAuthenticatorOptions">Options to configure the authentication request.</param>
		/// <returns>Returns a result parsed out from the callback url.</returns>
#if !NETSTANDARD
		[System.Runtime.Versioning.UnsupportedOSPlatform("windows")]
#endif
		public static Task<WebAuthenticatorResult> AuthenticateAsync(WebAuthenticatorOptions webAuthenticatorOptions)
			=> Current.AuthenticateAsync(webAuthenticatorOptions);
 
		static IWebAuthenticator Current => Authentication.WebAuthenticator.Default;
 
		static IWebAuthenticator? defaultImplementation;
 
		/// <summary>
		/// Provides the default implementation for static usage of this API.
		/// </summary>
		public static IWebAuthenticator Default =>
			defaultImplementation ??= new WebAuthenticatorImplementation();
 
		internal static void SetDefault(IWebAuthenticator? implementation) =>
			defaultImplementation = implementation;
	}
 
	/// <summary>
	/// This class contains static extension methods for use with <see cref="WebAuthenticator"/>.
	/// </summary>
	public static class WebAuthenticatorExtensions
	{
		static IPlatformWebAuthenticatorCallback AsPlatformCallback(this IWebAuthenticator webAuthenticator)
		{
			if (webAuthenticator is not IPlatformWebAuthenticatorCallback platform)
				throw new PlatformNotSupportedException("This implementation of IWebAuthenticator does not implement IPlatformWebAuthenticatorCallback.");
			return platform;
		}
 
#if ANDROID
		internal static bool IsAuthenticatingWithCustomTabs(this IWebAuthenticator webAuthenticator)
			=> (webAuthenticator as WebAuthenticatorImplementation)?.AuthenticatingWithCustomTabs ?? false;
#endif
 
		/// <summary>
		/// Begin an authentication flow by navigating to the specified url and waiting for a callback/redirect to the callbackUrl scheme.
		/// </summary>
		/// <param name="webAuthenticator">The <see cref="IWebAuthenticator"/> to use for the authentication flow.</param>
		/// <param name="url"> Url to navigate to, beginning the authentication flow.</param>
		/// <param name="callbackUrl"> Expected callback url that the navigation flow will eventually redirect to.</param>
		/// <returns>Returns a result parsed out from the callback url.</returns>
		public static Task<WebAuthenticatorResult> AuthenticateAsync(this IWebAuthenticator webAuthenticator, Uri url, Uri callbackUrl) =>
			webAuthenticator.AuthenticateAsync(new WebAuthenticatorOptions { Url = url, CallbackUrl = callbackUrl });
 
#if IOS || MACCATALYST || MACOS
		/// <inheritdoc cref="IPlatformWebAuthenticatorCallback.OpenUrlCallback(Uri)"/>
		public static bool OpenUrl(this IWebAuthenticator webAuthenticator, Uri uri) =>
			webAuthenticator.AsPlatformCallback().OpenUrlCallback(uri);
 
		/// <inheritdoc cref="ApplicationModel.Platform.OpenUrl(UIKit.UIApplication, Foundation.NSUrl, Foundation.NSDictionary)"/>
		public static bool OpenUrl(this IWebAuthenticator webAuthenticator, UIKit.UIApplication app, Foundation.NSUrl url, Foundation.NSDictionary options) 
		{
			if(url?.AbsoluteString != null)
			{
				return webAuthenticator.OpenUrl(new Uri(url.AbsoluteString));
			}
			return false;
		}
 
		/// <inheritdoc cref="ApplicationModel.Platform.ContinueUserActivity(UIKit.UIApplication, Foundation.NSUserActivity, UIKit.UIApplicationRestorationHandler)"/>
		public static bool ContinueUserActivity(this IWebAuthenticator webAuthenticator, UIKit.UIApplication application, Foundation.NSUserActivity userActivity, UIKit.UIApplicationRestorationHandler completionHandler)
		{
			var uri = userActivity?.WebPageUrl?.AbsoluteString;
			if (string.IsNullOrEmpty(uri))
				return false;
 
			return webAuthenticator.OpenUrl(new Uri(uri));
		}
 
#elif ANDROID
		/// <inheritdoc cref="IPlatformWebAuthenticatorCallback.OnResumeCallback(Android.Content.Intent)"/>
		public static bool OnResume(this IWebAuthenticator webAuthenticator, Android.Content.Intent intent) =>
			webAuthenticator.AsPlatformCallback().OnResumeCallback(intent);
#endif
	}
 
	/// <summary>
	/// Represents additional options for <see cref="WebAuthenticator"/>.
	/// </summary>
	public class WebAuthenticatorOptions
	{
		/// <summary>
		/// Gets or sets the URL that will start the authentication flow.
		/// </summary>
		public Uri? Url { get; set; }
 
		/// <summary>
		/// Gets or sets the callback URL that should be called when authentication completes.
		/// </summary>
		public Uri? CallbackUrl { get; set; }
 
		/// <summary>
		/// Gets or sets whether the browser used for the authentication flow is short-lived.
		/// This means it will not share session nor cookies with the regular browser on this device if set the <see langword="true"/>. 
		/// </summary>
		/// <remarks>This setting only has effect on iOS.</remarks>
		public bool PrefersEphemeralWebBrowserSession { get; set; }
 
		/// <summary>
		/// Gets or sets the decoder implementation used to decode the incoming authentication result URI. 
		/// </summary>
		public IWebAuthenticatorResponseDecoder? ResponseDecoder { get; set; }
	}
}