Web-based login on WPF projects for Azure Mobile Services

In almost all supported client platforms for Azure Mobile Services, there are two flavors of the login operation. The first one the client talks to an SDK specific to the login provider (i.e., the Live Connect SDK for Windows Store apps, the Facebook SDK for iOS, and so on) and then uses a token which they got from that provider to login to their mobile service – that's called the client-side authentication flow, since all the authentication action happens without interaction with the mobile service. On the other alternative, called server-side authentication flow, the client opens a web browser window (or control) which talks, via the mobile service runtime, to the provider web login interface, and after a series of redirects (which I depicted in a previous post) the client gets the authentication token from the service which will be used in subsequent (authenticated) calls. There’s one platform, however, which doesn’t have this support - “full” .NET 4.5 (i.e., the “desktop” version of the framework).

That platform is lacking that functionality because there are cases where it cannot display a web interface where the user can enter their credentials. For example, it can be used in a backend service (in which really there’s no user interface to interact with, like the scenario I showed in the post about a MVC app accessing authenticated tables in an Azure Mobile Service). It can also be a console application, in which there’s no “native” way to display a web page. Even if we could come up with a way to display a login page (such as in a popup window), what kind of window to use? If we go with WPF, it wouldn’t look natural in a WinForms app, and vice-versa.

We can, however, solve this problem if we constrain the platform to one specific which supports user interface elements. In this post, I’ll show how this can be done for a WPF project. Notice that my UI skills are really, really poor, so if you plan on using it on a “real” app, I’d strongly recommend you to refine the interface. To write the code for this project, I took as a starting point the actual code for the login page from the client SDK (I used the Windows Phone as an example) – it’s good to have all the client code publicly available.

The server-side authentication flow

Borrowing a picture I used in the post about authentication, this is what happens in the server-side authentication flow, where the client shows what the browser control in each specific platform does.

3113_ServerSideAuthFlow_1225ED52

What we then need to do is to have a web browser navigate to //mobileservicename.azure-mobile.net/login/<provider>, and monitor its redirects until it reaches /login/done. Once that’s reached, we can then parse the token and create the MobileServiceUser which will be set in the client.

The library

The library will have one extension method on the MobileServiceClient method, which will display our own login page (as a modal dialog / popup). When the login finishes successfully (if it fails it will throw an exception), we then parse the token returned by the login page, create the MobileServiceUser object, set it to the client, and return the user (using the same method signature as in the other platforms).

  1. public async static Task<MobileServiceUser> LoginAsync(this MobileServiceClient client, MobileServiceAuthenticationProvider provider)
  2. {
  3.     Uri startUri = new Uri(client.ApplicationUri, "login/" + provider.ToString().ToLowerInvariant());
  4.     Uri endUri = new Uri(client.ApplicationUri, "login/done");
  5.     LoginPage loginPage = new LoginPage(startUri, endUri);
  6.     string token = await loginPage.Display();
  7.     JObject tokenObj = JObject.Parse(token.Replace("%2C", ","));
  8.     var userId = tokenObj["user"]["userId"].ToObject<string>();
  9.     var authToken = tokenObj["authenticationToken"].ToObject<string>();
  10.     var result = new MobileServiceUser(userId);
  11.     result.MobileServiceAuthenticationToken = authToken;
  12.     client.CurrentUser = result;
  13.     return result;
  14. }

The login page is shown below. To mimic the login page shown at Windows Store apps, I’ll have a page with a header, a web view and a footer with a cancel button:

LoginPageDesigner

To keep the UI part simple, I’m using a simple grid:

  1. <Grid Name="grdRootPanel">
  2.     <Grid.RowDefinitions>
  3.         <RowDefinition Height="80"/>
  4.         <RowDefinition Height="*"/>
  5.         <RowDefinition Height="80"/>
  6.     </Grid.RowDefinitions>
  7.     <TextBlock Text="Connecting to a service..." VerticalAlignment="Center" HorizontalAlignment="Center"
  8.                FontSize="30" Foreground="Gray" FontWeight="Bold"/>
  9.     <Button Name="btnCancel" Grid.Row="2" Content="Cancel" HorizontalAlignment="Left" VerticalAlignment="Stretch"
  10.             Margin="10" FontSize="25" Width="100" Click="btnCancel_Click" />
  11.     <ProgressBar Name="progress" IsIndeterminate="True" Visibility="Collapsed" Grid.Row="1" />
  12.     <WebBrowser Name="webControl" Grid.Row="1" Visibility="Collapsed" />
  13. </Grid>

In the LoginPage.xaml.cs, the Display method (called by the extension method shown above) creates a Popup window, adds the login page as the child and shows it; when the popup is closed, it will either throw an exception if the login was cancelled, or return the stored token if successful.

  1. public Task<string> Display()
  2. {
  3.     Popup popup = new Popup();
  4.     popup.Child = this;
  5.     popup.PlacementRectangle = new Rect(new Size(SystemParameters.FullPrimaryScreenWidth, SystemParameters.FullPrimaryScreenHeight));
  6.     popup.Placement = PlacementMode.Center;
  7.     TaskCompletionSource<string> tcs = new TaskCompletionSource<string>();
  8.     popup.IsOpen = true;
  9.     popup.Closed += (snd, ea) =>
  10.     {
  11.          if (this.loginCancelled)
  12.         {
  13.             tcs.SetException(new InvalidOperationException("Login cancelled"));
  14.         }
  15.         else
  16.         {
  17.             tcs.SetResult(this.loginToken);
  18.         }
  19.     };
  20.  
  21.     return tcs.Task;
  22. }

The navigation starts at the constructor of the login page (it could be moved elsewhere, but since it’s primarily used by the extension method itself, it can start navigating to the authentication page as soon as possible).

  1. public LoginPage(Uri startUri, Uri endUri)
  2. {
  3.     InitializeComponent();
  4.  
  5.     this.startUri = startUri;
  6.     this.endUri = endUri;
  7.  
  8.     var bounds = Application.Current.MainWindow.RenderSize;
  9.     // TODO: check if those values work well for all providers
  10.     this.grdRootPanel.Width = Math.Max(bounds.Width, 640);
  11.     this.grdRootPanel.Height = Math.Max(bounds.Height, 480);
  12.  
  13.     this.webControl.LoadCompleted += webControl_LoadCompleted;
  14.     this.webControl.Navigating += webControl_Navigating;
  15.     this.webControl.Navigate(this.startUri);
  16. }

When the Navigating event of the web control is called, we can then check if the URI which the control is navigating to is the “final” URI which the authentication flow expects. If it’s the case, then we extract the token value, storing it in the object, and close the popup (which will signal the task on the Display method to be completed).

  1. void webControl_Navigating(object sender, NavigatingCancelEventArgs e)
  2. {
  3.     if (e.Uri.Equals(this.endUri))
  4.     {
  5.         string uri = e.Uri.ToString();
  6.         int tokenIndex = uri.IndexOf("#token=");
  7.         if (tokenIndex >= 0)
  8.         {
  9.             this.loginToken = uri.Substring(tokenIndex + "#token=".Length);
  10.         }
  11.         else
  12.         {
  13.             // TODO: better error handling
  14.             this.loginCancelled = true;
  15.         }
  16.  
  17.         ((Popup)this.Parent).IsOpen = false;
  18.     }
  19. }

That’s about it. There is some other code in the library (handling cancel, for example), but I’ll leave it out of this post for simplicity sake (you can still see the full code in the link at the bottom of this post).

Testing the library

To test the library, I’ll create a WPF project, with a button an a text area to write some debug information:

TestWindow

And on the click handler, we can invoke the LoginAsync; to test it out I’m also invoking an API which I set with user permissions, to make sure that the token which we got is actually good.

  1. private async void btnStart_Click(object sender, RoutedEventArgs e)
  2. {
  3.     try
  4.     {
  5.         var user = await MobileService.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
  6.         AddToDebug("User: {0}", user.UserId);
  7.         var apiResult = await MobileService.InvokeApiAsync("user");
  8.         AddToDebug("API result: {0}", apiResult);
  9.     }
  10.     catch (Exception ex)
  11.     {
  12.         AddToDebug("Error: {0}", ex);
  13.     }
  14. }

That’s it. The full code for this post can be found in the GitHub repository at https://github.com/carlosfigueira/blogsamples/tree/master/AzureMobileServices/AzureMobile.AuthExtensions. As usual, please let us know (either via comments in this blog or in our forums) if you have any issues, comments or suggestions.