Personalized Experiences with Azure Mobile Apps Authentication

James Montemagno

mobile appsThere’s a plethora of amazing options available to Xamarin developers when it’s time to add a cloud connected backend to your mobile app. Recently, I created a small application called <a href="http://github.com/jamesmontemagno/app-coffeecups">Coffee Cups leveraging Azure Mobile Apps’ Easy Tables to create a Xamarin.Forms mobile app on iOS, Android, and Windows 10 (UWP) with full online and offline synchronization in under 45 minutes. When developing mobile apps, we may want to share some of the data across all installs of the application, but some data may be specific to the user. For instance, with Coffee Cups, which I use to track how many cups of coffee I drink in a given day, I would only want to see my data, not data for everyone who is using the app. This is where adding authentication to the mobile app comes in, and it’s a breeze with Azure Mobile Apps.

Leveraging the Coffee Cups application that we developed earlier, we already have a base application to add user authentication to. First, let’s update our app to handle the user authentication workflow to prompt our users to log in, store authentication tokens, and pull data specific to our user.

Adding Identity to Data

The first update that we have to do is to add an identification property to our data models that need to be stored for a specific user. This can be achieved by adding a new string property to our CupOfCoffee model and will be used later when we update our backend logic:

[Newtonsoft.Json.JsonProperty("userId")]
public string UserId {get;set;}

Implementing an Authentication Dialog

When our users attempt to add a cup of coffee for the first time or refresh the data, we’ll want to prompt them to log in to an account. Azure Mobile Apps offers several types of authentication including popular social networks such as Twitter, Facebook, Microsoft, and Google in addition to Azure Active Directory. On top of that, you can connect to your own custom authentication. No matter which option you select, the workflow to prompt your user to authenticate is the same. To be able to ask our users to log in from our shared code, we will define a simple interface that can be called to trigger the platform specific implementation of authentication that is provided by the Azure Mobile Client SDK.

public interface IAuthentication
{
    Task LoginAsync(MobileServiceClient client, MobileServiceAuthenticationProvider provider);
}

Our login method will pass down the current Mobile Service Client and the type of provider that we want to use.

Saving Authentication Tokens

Before we implement our IAuthentication on each platform, we’ll want to have a way of caching the user’s tokens so that all future calls from the app are authenticated and the user only has to be prompted to log in once. For this we can use the Settings Plugin for Xamarin and Windows, which allows us to save and retrieve user specific settings from shared code. After installing the Settings Plugin in our portable class library project and all of the client projects, we can create a few properties to save and retrieve the user data in our shared code:

public static class Settings
{
    private static ISettings AppSettings => CrossSettings.Current;

    const string UserIdKey = "userid";
    static readonly string UserIdDefault = string.Empty;

    const string AuthTokenKey = "authtoken";
    static readonly string AuthTokenDefault = string.Empty;

    public static string AuthToken
    {
        get { return AppSettings.GetValueOrDefault(AuthTokenKey, AuthTokenDefault); }
        set  { AppSettings.AddOrUpdateValue(AuthTokenKey, value); }
    }

    public static string UserId
    {
        get { return AppSettings.GetValueOrDefault(UserIdKey, UserIdDefault);  }
        set { AppSettings.AddOrUpdateValue(UserIdKey, value); }
    }

    public static bool IsLoggedIn => !string.IsNullOrWhiteSpace(UserId); 
}

These settings are stored directly in each operating system’s preference systems such as NSUserDefaults or SharedPreferences. These settings are stored in clear text, so you may want to think about additional security if you want to store secret data.

Adding authentication to each of our mobile apps is as simple as calling our Mobile Service Client’s LoginAsync method and passing it a few parameters.

Mobile App Authentication

In both the iOS and Android projects, we will create a new class called Authentication, which implements IAuthentication and is exported utilizing Xamarin.Forms’ dependency service.

iOS Authentication:

[assembly:Dependency(typeof(Authentication))]
namespace CoffeeCups.iOS
{
    public class Authentication : IAuthentication
    {
        public async Task LoginAsync(MobileServiceClient client, MobileServiceAuthenticationProvider provider)
        {
            try
            {
                //attempt to find root view controller to present authentication page
                var window = UIKit.UIApplication.SharedApplication.KeyWindow;
                var root = window.RootViewController;
                if(root != null)
                {
                    var current = root;
                    while(current.PresentedViewController != null)
                    {
                        current = current.PresentedViewController;
                    }

	           //login and save user status
                    var user = await client.LoginAsync(current, provider);
                    Settings.AuthToken = user?.MobileServiceAuthenticationToken ?? string.Empty;
                    Settings.UserId = user?.UserId ?? string.Empty;

                    return user;
                }
            }
            catch(Exception e)
            {
                e.Data["method"] = "LoginAsync";
                Xamarin.Insights.Report(e);
            }

            return null;
        }
    }
}

Android Authentication:

[assembly:Dependency(typeof(Authentication))]
namespace CoffeeCups.Droid
{
    public class Authentication : IAuthentication
    {
        public async Task LoginAsync(MobileServiceClient client, MobileServiceAuthenticationProvider provider)
        {
            try
            {
                //login and save user status
                var user = await client.LoginAsync(Forms.Context, provider);
                Settings.AuthToken = user?.MobileServiceAuthenticationToken ?? string.Empty;
                Settings.UserId = user?.UserId ?? string.Empty;
                return user;
            }
            catch(Exception e)
            {
                e.Data["method"] = "LoginAsync";
                Xamarin.Insights.Report(e);
            }

            return null;
        }
    }
}

Windows 10 (UWP) Authentication:

[assembly:Dependency(typeof(Authentication))]
namespace CoffeeCups.UWP
{
    public class Authentication : IAuthentication
    {
        public async Task LoginAsync(MobileServiceClient client, MobileServiceAuthenticationProvider provider)
        {
            try
            {
                var user = await client.LoginAsync(provider);

                Settings.AuthToken = user?.MobileServiceAuthenticationToken ?? string.Empty;
                Settings.UserId = user?.UserId ?? string.Empty;

                return user;
            }
            catch(Exception e)
            {
                e.Data["method"] = "LoginAsync";
                Xamarin.Insights.Report(e);
            }

            return null;
        }
    }
}

Prompting User To Log In

Now that each platform has the Authentication interface implemented, we can ask our user to log in when they attempt to add a coffee. In our LoadCoffeesCommand and AddCoffeeCommand which are called when loading or adding coffee in the app, we can check to see if the user needs to log in and open the authentication dialog:

if(!Settings.IsLoggedIn)
{
    await azureService.Initialize();
    var authenticator = await DependencyService.Get();
    var user = authenticator.LoginAsync(azureService.MobileService, MobileServiceAuthenticationProvider.MicrosoftAccount);
    if(user == null)
        return;

    //pull latest data from server:
    var coffees = await azureService.GetCoffees();
    Coffees.ReplaceRange(coffees);
    SortCoffees();
}

In this instance, I pass in the Mobile Service Client and to use the Microsoft Account provider. If the user logs in successfully, a new MobileServiceUser will be returned, else it will be null.

Authentication Headers & Cached Credentials

The final step in our client code is to update our Mobile Service Client with a new HttpDelegateHandler to handle settings authentication headers and refresh the tokens. In our shared code, we’ll create a new AuthHandler.cs class to handle this:

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

    protected override async Task SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
    {
        if (this.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)
        {
            // Oh no! user is not logged in - we got a 401
            // Log them in, this time hardcoded with MicrosoftAccount but you would
            // trigger the login presentation in your application
            try
            {
                var user = await this.Client.LoginAsync(MobileServiceAuthenticationProvider.MicrosoftAccount, null);
                // we're now logged in again.

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


                Settings.UserId = user.UserId;
                Settings.AuthToken = user.MobileServiceAuthenticationToken;

                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 let’s return the original response
                return response;
            }
        }

        return response;
    }

    private async Task 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 != null && request.Content.Headers.ContentType != null)
        {
            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;
    }
}

We can now update our initialization code of the MobileServiceClient to not only use this AuthHandler, but also to use any cached credentials on startup:

var handler = new AuthHandler();
//Create our client and pass in Authentication handler
MobileService = new MobileServiceClient("https://mycoffeeapp.azurewebsites.net", handler);

//assign mobile client to handler
handler.Client = MobileService;

MobileService.CurrentUser = new MobileServiceUser (Settings.UserId);
MobileService.CurrentUser.MobileServiceAuthenticationToken = Settings.AuthToken;

Updating our Azure Backend

While the code in our mobile apps is complete, we still have to update our Azure Mobile App backend, set up authentication on our Easy Table, and add Microsoft Account Authentication.

We can track which logged in user added the data by adding a new userId column to our table, adding authentication, and updating our CRUD scripts on our backend. In the Azure Portal, find your Azure Mobile App and in the settings find Easy Tables. This will bring you to all of your tables that you have created and you are able to manually manage the schema of each to add new columns.

ManageSchema

Add a new string column with the name of userId that matches the name of the property we added to the CupOfCoffee in our app.

AddColumn

Next, select Edit Script, which will open Visual Studio Online’s Monaco source code IDE. This will allow you to update the scripts in the backend to set and retrieve the Cups of Coffee for the logged in user by leveraging the new userId column we added. If you are using a .NET backend, be sure to read through the documentation to add user authentication on a per controller basis.

Modify js file

Under the tables folder you will find a javascript file that we’ll update to set the userId field:

var table = module.exports = require('azure-mobile-apps').table();

// Configure specific code when the client does a request
// READ - only return records belonging to the authenticated user
table.read(function (context) {
   context.query.where({ userId: context.user.id });
   return context.execute();
 });

// CREATE - add or overwrite the userId based on the authenticated user
 table.insert(function (context) {
   context.item.userId = context.user.id;
   return context.execute();
 });

// UPDATE - for this scenario, we don't need to do anything - this is
// the default version
table.update(function (context) {
  return context.execute();
});

// DELETE - for this scenario, we don't need to do anything - this is
// the default version
table.delete(function (context) {
  return context.execute();
});

module.exports = table;

Back on the CupOfCoffee table, we’ll need to adjust the permission settings to only allow authenticated access to perform any operation:

ModifyAccess

We’re nearly done, all we need to do is set up the authentication provider, Microsoft Account, which can be found under the Mobile Authentication column in the Settings. Here you will find a list of providers that can be used with Azure Mobile Apps, or you can use your own. I followed the Microsoft Account guide to add a new app and get the Client Id and Secret to add to configure the provider in the Azure Portal.

MobileAuthentication

Run the Apps

It’s finally time to run the apps and be prompted to log in when we attempt to add a cup of coffee! Returning to the app will automatically log the user back in and pull their data into the app.

Login

Learn More

In this post we’ve covered setting up the basic authentication using a Microsoft Account and Azure Mobile App’s Easy Tables. Be sure to check out all of the additional services provided by Azure Mobile App Services, such as authentication and push notifications on the Azure portal. You can try out Azure Mobile Apps by downloading this Coffee Cups sample from my GitHub or by testing prebuilt sample apps on the Try App Service page.

0 comments

Discussion is closed.

Feedback usabilla icon