Getting to Know Azure Mobile App Cont.

Microsoft Azure Mobile App has recently gone GA (General Availability) and has definitely captured my attention. Mobile App is a tremendous accelerator that enables us to go from an idea to a functional prototype quickly. Then, we can continue to build on that initial investment to create a robust production ready app. Finally, this post is all about using Visual Studio Team Services (VSTS) to build and publish apps to HockeyApp, so that we can test and assess quality before our apps make it to our favorite app Stores.

Refreshing Authentication Tokens

Authentication Tokens are short-lived and having users login to the App frequently can cause friction. This is definitely undesirable and can be dealt with by identifying when a Token is no longer valid. When this condition is met, we can attempt to refresh the Authentication Token by calling the Azure App Service Token Store APIs.

The following shows how to wire-up the MobileApp AuthHandler.

Using the AuthHandler

public App()
{
    InitializeComponent();

    SplashFactory = e => new Views.Splash(e);

    // Bind the Mobile Service Client to the Auth handler
    AuthHandler.Client = MobileService;
}

private static readonly AuthHandler AuthHandler = new AuthHandler(
    user =>
    {
        var vault = new PasswordVault();
        vault.Add(new PasswordCredential("AskAlex",
            user.UserId,
            user.MobileServiceAuthenticationToken));
    });

public static readonly MobileServiceClient MobileService = new MobileServiceClient("https://askalex.azurewebsites.net/", AuthHandler);

The AuthHandler

I Implemented this AuthHandler based on the examples found in caching and handling expired tokens in Azure Mobile Services managed SDK. I used a post about the App Service Token Store to find an example I could reuse to refresh the Authentication Token.

The App Service Token Store allows us to retrieve stored Tokens, refresh Tokens against the origin Identity Provider. Find out more about the feature in-depth from this post on Architecture of Azure App Service Authentication / Authorization.

internal class AuthHandler : DelegatingHandler
{
    public IMobileServiceClient Client { get; set; }

    private readonly Action<MobileServiceUser> saveUserDelegate;

    public AuthHandler(Action<MobileServiceUser> saveUserDelegate)
    {
        this.saveUserDelegate = saveUserDelegate;
    }

    protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage request,
        CancellationToken cancellationToken)
    {
        if (Client == null)
            throw new InvalidOperationException(
                "Make sure to set the 'Client' property in this handler before using it.");

        // Cloning the request, in case we need to send it again
        var clonedRequest = await CloneRequest(request);
        var response = await base.SendAsync(clonedRequest, cancellationToken);

        if (response.StatusCode != HttpStatusCode.Unauthorized)
            return response;

        // User is not logged in – we got a 401
        try
        {
            MobileServiceUser user = Client.CurrentUser;

            if (user == null)
            {
                // prompt with login UI
                user = await Client.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
            }
            else
            {
                // Refresh Tokens
                try
                {
                    EngagementAgent.Instance.StartActivity("RefreshToken");

                    // Calling /.auth/refresh will update the tokens in the token store
                    // and will also return a new mobile authentication token.
                    JObject refreshJson = (JObject) await App.MobileService.InvokeApiAsync("/.auth/refresh",
                        HttpMethod.Get,
                        null);

                    string newToken = refreshJson["authenticationToken"].Value<string>();
                    user.MobileServiceAuthenticationToken = newToken;
                }
                catch
                {
                    EngagementAgent.Instance.StartActivity("Login");
                    user = await Client.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount);
                }
            }

            // we're now logged in again.

            // Save the user to the app settings
            saveUserDelegate(user);

            // Clone the request
            clonedRequest = await CloneRequest(request);

            clonedRequest.Headers.Remove("X-ZUMO-AUTH");

            // Set the authentication header
            clonedRequest.Headers.Add("X-ZUMO-AUTH", user.MobileServiceAuthenticationToken);

            // Resend the request
            response = await base.SendAsync(clonedRequest, cancellationToken);
        }
        catch (InvalidOperationException)
        {
            // user cancelled auth, so lets return the original response
            return response;
        }

        return response;
    }

    private async Task<HttpRequestMessage> CloneRequest(HttpRequestMessage request)
    {
        var result = new HttpRequestMessage(request.Method, request.RequestUri);
        foreach (var header in request.Headers)
        {
            result.Headers.Add(header.Key, header.Value);
        }

        if (request.Content?.Headers.ContentType == null)
            return result;

        var requestBody = await request.Content.ReadAsStringAsync();
        var mediaType = request.Content.Headers.ContentType.MediaType;
        result.Content = new StringContent(requestBody, Encoding.UTF8, mediaType);
        foreach (var header in request.Content.Headers)
            if (!header.Key.Equals("Content-Type", StringComparison.OrdinalIgnoreCase))
                result.Content.Headers.Add(header.Key, header.Value);

        return result;
    }
}

Share your thoughts in the comments below

In future posts, I will continue to implements various aspects of the Ask Alex Universal Windows Platform (UWP) Mobile App.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

This site uses Akismet to reduce spam. Learn how your comment data is processed.