Damian Mehers' Blog Android, VR and Wearables from Geneva, Switzerland.

4Sep/160

UWP OAuth in Xamarin Forms using Xamarin.Auth

tl;dr: Complete standalone example here.

I recently wanted to authenticate to Evernote via OAuth in a Xamarin Forms app I'm creating.

There is an excellent Xamarin plugin, called Xamarin.Auth which lets you do the OAuth dance for iOS and Android Xamarin Forms apps, but even in the latest branch, I couldn't get it working on the Universal Windows Platform (UWP) app.

Comments pointed to using a the WebAuthenticationBroker from Microsoft. There are plenty of examples here, unfortunately none for Evernote.

I created and published an Evernote UWP OAuth example using WebAuthenticationBroker based on the Twitter example, which was similar, but not similar enough to be able to just copy/paste.

Once I had this working I was back to Xamarin Forms,and put together a complete standalone example using Google, to log you in to Google and then display your email address and photo. Here it is running in UWP:

uwp 04

I published that example on GitHub.

In order to make Xamarin.Auth work, you create a platform specific page renderer which does the OAuth. I'd already done this for iOS and Android. For Windows I implemented it like this:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Threading.Tasks;
using Windows.Security.Authentication.Web;
using Windows.Web.Http;
using Newtonsoft.Json;
using Xamarin.Auth;
using Xamarin.Forms.Platform.UWP;
using XamFormsUWPOAuth;
using XamFormsUWPOAuth.Shared;
using XamFormsUWPOAuth.UWP;

[assembly: ExportRenderer(typeof(AuthenticationPage), typeof(AuthenticationPageRenderer))]

namespace XamFormsUWPOAuth.UWP {
  class AuthenticationPageRenderer : PageRenderer {
    private bool _isShown;

    protected override async void OnElementPropertyChanged(object sender, PropertyChangedEventArgs e) {
      base.OnElementPropertyChanged(sender, e);
      if (_isShown) return;
      _isShown = true;

      var code = await AuthenticateUsingWebAuthenticationBroker();
      var account = await ConvertCodeToAccount(code);
      await AuthenticationHelper.FetchGoogleEmailAndPicture(account);
    }


    private async Task<string> AuthenticateUsingWebAuthenticationBroker() {
      var googleUrl = Constants.AuthorizeUrl + "?client_id=" +
                      Uri.EscapeDataString(Constants.GoogleClientId);
      googleUrl += "&redirect_uri=" + Uri.EscapeDataString(Constants.GoogleCallbackUrl);
      googleUrl += "&response_type=code";
      googleUrl += "&scope=" + Uri.EscapeDataString(Constants.Scope);

      var startUri = new Uri(googleUrl);

      var webAuthenticationResult =
        await
          WebAuthenticationBroker.AuthenticateAsync(WebAuthenticationOptions.None, startUri,
            new Uri(Constants.GoogleCallbackUrl));
      return webAuthenticationResult.ResponseStatus != WebAuthenticationStatus.Success ? null : webAuthenticationResult.ResponseData.Substring(webAuthenticationResult.ResponseData.IndexOf('=') + 1);
    }


    private static async Task<Account> ConvertCodeToAccount(string code) {
      var httpClient = new HttpClient();
      IHttpContent content = new HttpFormUrlEncodedContent(new Dictionary<string, string> {
        {"code", code},
        {"client_id", Constants.GoogleClientId},
        {"client_secret", Constants.GoogleClientSecret},
        {"redirect_uri", Constants.GoogleCallbackUrl},
        {"grant_type", "authorization_code"},
      });
      var accessTokenResponse = await httpClient.PostAsync(new Uri(Constants.AccessTokenUrl), content);
      var responseDict =
        JsonConvert.DeserializeObject<Dictionary<string, string>>(accessTokenResponse.Content.ToString());

      return new Account(null, responseDict);
    }
  }
}

You could use one of the other Microsoft OAuth examples, or your own, in order to do the OAuth.

The AccountStore stuff is a little different. I wanted to reuse the AccountStore goodness that comes with Xamarin.Auth, but I needed a UWP AccountStore implementation. I also needed to ensure my shared code, in my shared project, picked up my UWP AccountStore. I did this by creating a simple container class in the shared project:

using System;
using Xamarin.Auth;

namespace XamFormsUWPOAuth.Shared {
public static class AccountStoreFactory {
    public static Func<AccountStore> Create { get; set; } = () => AccountStore.Create();
  }
}

I use this in order to get at the shared AccountStore throughout my code, rather than using AccountStore.Create() which you'd normally do. In my UWP startup code, I overwrite the default AccountStore (which doesn't exist on UWP anyway) in my App.xaml.cs :

    protected override void OnLaunched(LaunchActivatedEventArgs e) {
      AccountStoreFactory.Create = () => new UWPAccountStore();
      Frame rootFrame = Window.Current.Content as Frame;
              ...

My UWP specific AccountStore implementation was based on this one in the portable-bait-and-switch branch)

It makes use of the Igor Kulman's DataProtectionExtension implementation here.

uwp 01

uwp 03

uwp 04

Check out the full standalone example that works with iOS, Android and of course UWP in my GitHub repository.

Filed under: Xamarin Leave a comment
Comments (0) Trackbacks (1)

Leave a comment