24 June 2015

Custom oAuth login to Facebook for Windows Store apps

Intro: the joy of third party dependencies
I am currently working on a Windows app that uses some kind of Facebook integration. Last Friday (June 20) something odd occurred: some POC code that I got from my colleague Melvin Vermeer where I had been toying with (the code, to be clear – not Melvin ;) ), suddenly stopped working. Assuming I had messed something up, I started tinkering with it, then checked the Facebook settings to see if Melvin had used some odd setting that only worked temporarily. This was not the case – so I even tried it on a different computer and later at home (operating on the assumption I had somehow blacklisted the Wortell offices). To no avail. The error I kept getting was:

Given URL is not permitted by the Application configuration
One or more of the given URLs is not permitted by the App's settings. It must match the Website URL or Canvas URL, or the domain must be a subdomain of one of the App's domains.

Not a good way to start a weekend, I can tell you. Then I decided to employ the ‘wisdom of the crowd’, aka twitter. My fellow MVP András Vélvart responded immediately by acknowledging he had the same problem, and pointed me to this Facebook bug report.

I hadn’t messed anything up, neither had Melvin. Facebook itself had pulled the rug from under us.

Now at the point of this writing I have ascertained Facebook apparently have fixed the error, but that was not the case yesterday and with a deadline looming I needed a plan B very quickly, and I got one with the help of Tamás Deme, aka 'tomzorz', a Hungarian Windows Phone consumer MVP that I did not knew nor followed yet (shame on me!). Although the ‘normal’ way now works again, I decided to blog about the alternative approach anyway, to make sure this plan B is available to everyone in case, ehm … excrement hits the cooling device … again.

The prescribed way of getting a Facebook access token
In a world where everything works as it should you can get a Facebook token using WebAuthenticationBroker and a Facebook C# SDK, which is also available as a NuGet package. The code I got from my colleague basically came down to this:

using Facebook;

namespace FacebookNormal
{
  public sealed partial class MainPage : Page
  {
    private const string AppId = "your app id here";
    private const string ExtendedPermissions = 
      "publish_actions, user_managed_groups, user_groups";

    public MainPage()
    {
      this.InitializeComponent();
    }

    private async void ButtonBase_OnClick(object sender, RoutedEventArgs e)
    {
      var result = await AuthenticateFacebookAsync();
      var md = new MessageDialog("your token is: " + result);
      await md.ShowAsync();
    }

    private async Task<string> AuthenticateFacebookAsync()
    {
      try
      {
        var fb = new FacebookClient();

        var redirectUri = 
          WebAuthenticationBroker.GetCurrentApplicationCallbackUri().ToString();

        var loginUri = fb.GetLoginUrl(new
                                       {
                                         client_id = AppId,
                                         redirect_uri = redirectUri,
                                         scope = ExtendedPermissions,
                                         display = "popup",
                                         response_type = "token"
                                       });

        var callbackUri = new Uri(redirectUri, UriKind.Absolute);

        var authenticationResult =
          await
            WebAuthenticationBroker.AuthenticateAsync(
            WebAuthenticationOptions.None, 
            loginUri, callbackUri);

        return ParseAuthenticationResult(fb, authenticationResult);
      }
      catch (Exception ex)
      {
        return ex.Message;
      }
    }

    public string ParseAuthenticationResult(FacebookClient fb, 
                                            WebAuthenticationResult result)
    {
      switch (result.ResponseStatus)
      {
        case WebAuthenticationStatus.ErrorHttp:
          return "Error";
        case WebAuthenticationStatus.Success:

          var oAuthResult = fb.ParseOAuthCallbackUrl(new Uri(result.ResponseData));
          return oAuthResult.AccessToken;
        case WebAuthenticationStatus.UserCancel:
          return "Operation aborted";
      }
      return null;
    }
  }
}

You get the callback URL to your own app, create a login Url, ask the WebAuthenticationBroker to do it’s stuff and show the “connecting to a service” window with the Facebook login, you parse the result, and if all goes well, you have a token. All code sits in the code behind – this was a POC, so that is very much OK.

Plan B – using a custom login employing a WebView

This looks very much the same, except that I have replaced both the AuthenticateFacebookAsync and the ParseAuthenticationResult methods.
private const string FbSuccess = 
  "https://www.facebook.com/connect/login_success.html";

private async Task<string> AuthenticateFacebookAsync()
{
  try
  {
    var fb = new FacebookClient();

    var loginUri = fb.GetLoginUrl(new
    {
      client_id = AppId,
      redirect_uri = FbSuccess,
      scope = ExtendedPermissions,
      display = "popup",
      response_type = "token"
    });

    var authenticationResult =
      await
        FacebookAuthenticationBroker.AuthenticateAsync(loginUri);

    return ParseAuthenticationResult(authenticationResult);
  }
  catch (Exception ex)
  {
    return ex.Message;
  }
}

private static string ParseAuthenticationResult(string authResult)
{
  var pattern = 
    string.Format("{0}#access_token={1}&expires_in={2}", 
                  FbSuccess,"(?<access_token>.+)", "(?<expires_in>.+)");
  var match = Regex.Match(authResult, pattern);
  return match.Groups["access_token"].Value;
}

Now what is that mysterious FacebookAuthenticationBroker? The framework for that I got from Tamás, and I added some stuff to it

namespace FacebookCustom
{
  /// 
  /// This class is a helper to replace the default WebAuthenticationBroker
  /// 
  public static class FacebookAuthenticationBroker
  {
    public static Task AuthenticateAsync(Uri uri)
    {
      var tcs = new TaskCompletionSource();

      var w = new WebView
      {
        HorizontalAlignment = HorizontalAlignment.Stretch,
        VerticalAlignment = VerticalAlignment.Stretch,
        Margin = new Thickness(30.0),
      };

      var b = new Border
      {
        Background = 
          new SolidColorBrush(Color.FromArgb(255, 255, 255, 255)),
        Width = Window.Current.Bounds.Width,
        Height = Window.Current.Bounds.Height,
        Child = w
      };

      var p = new Popup
      {
        Width = Window.Current.Bounds.Width,
        Height = Window.Current.Bounds.Height,
        Child = b,
        HorizontalOffset = 0.0,
        VerticalOffset = 0.0
      };

      Window.Current.SizeChanged += (s, e) =>
          {
            p.Width = e.Size.Width;
            p.Height = e.Size.Height;
            b.Width = e.Size.Width;
            b.Height = e.Size.Height;
          };

      w.Source = uri;

      w.NavigationCompleted += (sender, args) =>
      {
        if (args.Uri != null)
        {
          if (args.Uri.OriginalString.Contains("access_token"))
          {
            tcs.SetResult(args.Uri.ToString());
            p.IsOpen = false;
          }
          if (args.Uri.OriginalString.Contains("error=access_denied"))
          {
            tcs.SetResult(null);
            p.IsOpen = false;
          }
        }
      };

      p.IsOpen = true;
      return tcs.Task;
    }
  }
}

A lot of it is just setting up a UI of a popup with a WebView, then navigating to the Facebook authentication url. This will show a ‘normal’ web Facebook login (without the “Connecting to service” header). The interesting part I annotated in red and bold – when this event handler detects either “access_token” or “error=access_denied” in the url it’s navigated to, it considers it’s work done. Facebook navigates to a page with an url that either contains one of these strings, and we have a token again. Or not.

I know, it’s crude, probably has a lot of edge cases, but it will get you through the day when time is tight ;)

One more thing
To get this to work, you will have to add in your Facebook app settings (on Facebook developer) under section “Settings/Advanced” the return url to whatever you decided to use for the Facebook success url ( see FbSuccess constant).

image

Conclusion
As always you can find a ready to run demo on GitHub although in this case ‘ready to run’ is stretching it a little, as you will need to define an app in Facebook developer to get this actually working. And a great big of thanks to my colleague and the awesome #wpdev community for getting this to work.

No comments: