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.