Wednesday, 8 April 2020

Combining Xamarin.Essentials WebAuthenticator with IdentityModel.OidcClient

Previously I wrote about using the Xamarin.Essentials WebAuthenticator class to initiate an authentication flow with IdentityServer, and wait for a callback/redirect to the redirect URI scheme. Although my blog post didn’t show it, there was an IdentityService class that created the URI for the authorization endpoint, including the code challenge, CSRF token, and other required query parameters. This class also handled exchanging the received authorization code for an access token, which could then be used in API calls.

While this approach works well, it does require a detailed knowledge of the OAuth 2.0 spec for native apps. While that’s certainly something I’d recommend understanding fully, it would also be convenient if there was a library that did the heavy lifting for you.

Enter IdentityModel.OidcClient

IdentityModel.OidcClient is a C#/.NET standard 2.0 OpenID certified library for native mobile and desktop applications. It’s a small, simple, and elegant library that I strongly recommend for communicating with IdentityServer.

The library has two modes:

  1. In manual mode, OidcClient helps you with creating the necessary start URL and state parameters, but you need to coordinate with the browser you want to use. Then, when the browser work is completed, OidcClient can take over to process the response, get the access/refresh tokens, and contact other endpoints.
  2. In automatic mode, you can encapsulate all browser interactions by implementing the IBrowser interface. Then, authentication and token requests become one line of code.

I’ve previously used manual mode for interacting with IdentityServer, when I had to write my own platform code for invoking native browsers. However, now that WebAuthenticator does that for you, it’s much simpler to use automatic mode and wrap WebAuthenticator using the IBrowser interface.

The IBrowser interface is used to model a browser, and specifies that a single method named InvokeAsync requires implementing, which returns a Task<BrowserResult>. The InvokeAsync method should just invoke your browser to initiate the authentication flow, and return the response in a BrowserResult object.

A Browser class that implements the IBrowser interface, which uses WebAuthenticator to provide the browser interactions, is shown below:

public class Browser : IBrowser
{
    public async Task InvokeAsync(BrowserOptions options, CancellationToken cancellationToken = default)
    {
        WebAuthenticatorResult authResult =
                await WebAuthenticator.AuthenticateAsync(new Uri(options.StartUrl), new Uri(Constants.RedirectUri));
        return new BrowserResult()
        {
            Response = ParseAuthenticatorResult(authResult)
        };
    }

    string ParseAuthenticatorResult(WebAuthenticatorResult result)
    {
        string code = result?.Properties["code"];
        string scope = result?.Properties["scope"];
        string state = result?.Properties["state"];
        string sessionState = result?.Properties["session_state"];
        return $"{Constants.RedirectUri}#code={code}&scope={scope}&state={state}&session_state={sessionState}";
    }
}

Here, the InvokeAsync method calls the WebAuthenticator.AuthenticateAsync method, and returns the parsed response in a BrowserResult object.

An authentication flow can then be initiated by a OidcClient object as follows:

OidcClient _oidcClient;
LoginResult _loginResult;

var options = new OidcClientOptions
{
    Authority = Constants.AuthorityUri,
    ClientId = Constants.ClientId,
    Scope = Constants.Scope,
    RedirectUri = Constants.RedirectUri,
    ResponseMode = OidcClientOptions.AuthorizeResponseMode.Redirect,
    Browser = new Browser()
};

_oidcClient = new OidcClient(options);
_loginResult = await _oidcClient.LoginAsync(new LoginRequest());

Here, an OidcClientOptions object is created that defines the precise authentication flow that will be invoked with IdentityServer. The OidcClientOptions object also defines the IBrowser object that's used to provide browser interactions. An OidcClient object is then created, and its LoginAsync method is invoked to initiate the authentication flow. The result of the authentication flow will then be stored in a LoginResult object, which can include different tokens (access, identity, refresh) along with other data.

The access token can then be used in subsequent API calls to IdentityServer:

HttpClient _httpClient;

_httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(Constants.ApiUri);
_httpClient.DefaultRequestHeaders.Authorization
    = new AuthenticationHeaderValue("Bearer", _loginResult?.AccessToken ?? string.Empty);

HttpResponseMessage response = await _httpClient.GetAsync("test");
string content = await response.Content.ReadAsStringAsync();

if (response.IsSuccessStatusCode)
{
    editor.Text = JArray.Parse(content).ToString();
}
else
{
    editor.Text = response.ReasonPhrase;
}

Here, the access token is retrieved from the LoginResult object and set as the bearer token in the Authorization header of a HttpClient object: The HttpClient object then makes a call to a specific IdentityServer API, using the access token for authorization.

Wrapping up

This blog post has shown how to combine the Xamarin.Essentials WebAuthenticator class with the IdentityModel.OidcClient library to initiate authentication flows, and process the response. The advantage of combining WebAuthenticator with OidcClient is that (1) you don’t have to write any platform code to invoke native browsers, and (2) you don’t need to write any code to generate the correct authentication query parameters. In addition, because OidcClient is OpenID certified, you have peace of mind that it’s correctly implemented the OAuth 2.0 spec for native apps. For more information about the IdentityModel.OidcClient library, see Building mobile/native clients in the IdentityModel docs.

The sample this code comes from can be found on GitHub.

No comments:

Post a comment