Web authenticator

This article describes how you can use the .NET Multi-platform App UI (.NET MAUI) the IWebAuthenticator interface. This interface lets you start browser-based authentication flows, which listen for a callback to a specific URL registered to the app.

The default implementation of the IWebAuthenticator interface is available through the WebAuthenticator.Default property. Both the IWebAuthenticator interface and WebAuthenticator class are contained in the Microsoft.Maui.Authentication namespace.

Overview

Many apps require adding user authentication, and this often means enabling your users to sign in to their existing Microsoft, Facebook, Google, or Apple Sign In account.

Tip

Microsoft Authentication Library (MSAL) provides an excellent turn-key solution to adding authentication to your app.

If you're interested in using your own web service for authentication, it's possible to use WebAuthenticator to implement the client-side functionality.

Why use a server back end

Many authentication providers have moved to only offering explicit or two-legged authentication flows to ensure better security. This means you'll need a client secret from the provider to complete the authentication flow. Unfortunately, mobile apps aren't a great place to store secrets and anything stored in a mobile app's code, binaries, or otherwise, is considered to be insecure.

The best practice here's to use a web backend as a middle layer between your mobile app and the authentication provider.

Important

We strongly recommend against using older mobile-only authentication libraries and patterns which do not leverage a web backend in the authentication flow, due to their inherent lack of security for storing client secrets.

Get started

To access the WebAuthenticator functionality the following platform-specific setup is required.

Android requires an Intent Filter setup to handle your callback URI. This is accomplished by inheriting from the WebAuthenticatorCallbackActivity class:

using Android.App;
using Android.Content.PM;

namespace YourNameSpace;

[Activity(NoHistory = true, LaunchMode = LaunchMode.SingleTop, Exported = true)]
[IntentFilter(new[] { Android.Content.Intent.ActionView },
              Categories = new[] { Android.Content.Intent.CategoryDefault, Android.Content.Intent.CategoryBrowsable },
              DataScheme = CALLBACK_SCHEME)]
public class WebAuthenticationCallbackActivity : Microsoft.Maui.Authentication.WebAuthenticatorCallbackActivity
{
    const string CALLBACK_SCHEME = "myapp";

}

If your project's Target Android version is set to Android 11 (R API 30) or higher, you must update your Android Manifest with queries that use Android's package visibility requirements.

In the Platforms/Android/AndroidManifest.xml file, add the following queries/intent nodes the manifest node:

<queries>
  <intent>
    <action android:name="android.support.customtabs.action.CustomTabsService" />
  </intent>
</queries>

Using WebAuthenticator

The API consists mainly of a single method, AuthenticateAsync, which takes two parameters:

  1. The URL used to start the web browser flow.
  2. The URI the flow is expected to ultimately call back to, that is registered to your app.

The result is a WebAuthenticatorResult, which includes any query parameters parsed from the callback URI:

try
{
    WebAuthenticatorResult authResult = await WebAuthenticator.Default.AuthenticateAsync(
        new Uri("https://mysite.com/mobileauth/Microsoft"),
        new Uri("myapp://"));

    string accessToken = authResult?.AccessToken;

    // Do something with the token
}
catch (TaskCanceledException e)
{
    // Use stopped auth
}

The WebAuthenticator API takes care of launching the url in the browser and waiting until the callback is received:

Typical web authentication flow.

If the user cancels the flow at any point, a TaskCanceledException is thrown.

Private authentication session

iOS 13 introduced an ephemeral web browser API for developers to launch the authentication session as private. This enables developers to request that no shared cookies or browsing data is available between authentication sessions and will be a fresh login session each time. This is available through the WebAuthenticatorOptions parameter passed to the AuthenticateAsync method:

try
{
    WebAuthenticatorResult authResult = await WebAuthenticator.Default.AuthenticateAsync(
        new WebAuthenticatorOptions()
        {
            Url = new Uri("https://mysite.com/mobileauth/Microsoft"),
            CallbackUrl = new Uri("myapp://"),
            PrefersEphemeralWebBrowserSession = true
        });

    string accessToken = authResult?.AccessToken;

    // Do something with the token
}
catch (TaskCanceledException e)
{
    // Use stopped auth
}

Platform differences

This section describes the platform-specific differences with the web authentication API.

Custom Tabs are used whenever available, otherwise an Intent is started for the URL.

Apple Sign In

According to Apple's review guidelines, if your app uses any social login service to authenticate, it must also offer Apple Sign In as an option. To add Apple Sign In to your apps, first you'll need to configure your app to use Apple Sign In.

For iOS 13 and higher, call the AppleSignInAuthenticator.AuthenticateAsync() method. This will use automatically the native Apple Sign in APIs so your users get the best experience possible on these devices. For example, you can write your shared code to use the correct API at runtime:

var scheme = "..."; // Apple, Microsoft, Google, Facebook, etc.
var authUrlRoot = "https://mysite.com/mobileauth/";
WebAuthenticatorResult result = null;

if (scheme.Equals("Apple")
    && DeviceInfo.Platform == DevicePlatform.iOS
    && DeviceInfo.Version.Major >= 13)
{
    // Use Native Apple Sign In API's
    result = await AppleSignInAuthenticator.AuthenticateAsync();
}
else
{
    // Web Authentication flow
    var authUrl = new Uri($"{authUrlRoot}{scheme}");
    var callbackUrl = new Uri("myapp://");

    result = await WebAuthenticator.Default.AuthenticateAsync(authUrl, callbackUrl);
}

var authToken = string.Empty;

if (result.Properties.TryGetValue("name", out string name) && !string.IsNullOrEmpty(name))
    authToken += $"Name: {name}{Environment.NewLine}";

if (result.Properties.TryGetValue("email", out string email) && !string.IsNullOrEmpty(email))
    authToken += $"Email: {email}{Environment.NewLine}";

// Note that Apple Sign In has an IdToken and not an AccessToken
authToken += result?.AccessToken ?? result?.IdToken;

Tip

For non-iOS 13 devices, this will start the web authentication flow, which can also be used to enable Apple Sign In on your Android and Windows devices. You can sign into your iCloud account on your iOS simulator to test Apple Sign In.

ASP.NET core server back end

It's possible to use the WebAuthenticator API with any web back-end service. To use it with an ASP.NET core app, configure the web app with the following steps:

  1. Set up your external social authentication providers in an ASP.NET Core web app.
  2. Set the Default Authentication Scheme to CookieAuthenticationDefaults.AuthenticationScheme in your .AddAuthentication() call.
  3. Use .AddCookie() in your Startup.cs .AddAuthentication() call.
  4. All providers must be configured with .SaveTokens = true;.
services.AddAuthentication(o =>
    {
        o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
    })
    .AddCookie()
    .AddFacebook(fb =>
    {
        fb.AppId = Configuration["FacebookAppId"];
        fb.AppSecret = Configuration["FacebookAppSecret"];
        fb.SaveTokens = true;
    });

Tip

If you'd like to include Apple Sign In, you can use the AspNet.Security.OAuth.Apple NuGet package. You can view the full Startup.cs sample.

Add a custom mobile auth controller

With a mobile authentication flow, you usually start the flow directly to a provider the user has chosen. For example, clicking a "Microsoft" button on the sign-in screen of the app. It's also important to return relevant information to your app at a specific callback URI to end the authentication flow.

To achieve this, use a custom API Controller:

[Route("mobileauth")]
[ApiController]
public class AuthController : ControllerBase
{
    const string callbackScheme = "myapp";

    [HttpGet("{scheme}")] // eg: Microsoft, Facebook, Apple, etc
    public async Task Get([FromRoute]string scheme)
    {
        // 1. Initiate authentication flow with the scheme (provider)
        // 2. When the provider calls back to this URL
        //    a. Parse out the result
        //    b. Build the app callback URL
        //    c. Redirect back to the app
    }
}

The purpose of this controller is to infer the scheme (provider) the app is requesting, and start the authentication flow with the social provider. When the provider calls back to the web backend, the controller parses out the result and redirects to the app's callback URI with parameters.

Sometimes you may want to return data such as the provider's access_token back to the app, which you can do via the callback URI's query parameters. Or, you may want to instead create your own identity on your server and pass back your own token to the app. What and how you do this part is up to you!

Check out the full controller sample.

Note

The above sample demonstrates how to return the access token from the 3rd party authentication (ie: OAuth) provider. To obtain a token you can use to authorize web requests to the web backend itself, you should create your own token in your web app, and return that instead. The Overview of ASP.NET Core authentication has more information about advanced authentication scenarios in ASP.NET Core.