Getting user information on Azure Mobile Services

With the introduction of the server-side authentication flow (which I mentioned in my last post), it’s now a lot simpler to authenticate users with Windows Azure Mobile Services. Once the LoginAsync / login / loginViewControllerWithProvider:completion: method / selector completes, the user is authenticated, the MobileServiceClient / MSClient object will hold a token that is used for authenticating requests, and it can now be used to access authentication-protected tables. But there’s more to authentication than just getting a unique identifier for a user – we can also get more information about the user from the providers we used to authenticate, or even act on their behalf if they allowed the application to do so.

With Azure Mobile Services you can still do this. However, the property is not available at the user object stored at the client – the only property it exposes is the user id, which doesn’t give the information that the user authorized the providers to give. This post will show, for the supported providers, how to get access to some of their properties, using their specific APIs.

User identities

The client objects doesn’t expose any of that information to the application, but at the server side, we can get what we need. The User object which is passed to all scripts has now a new function, getIdentities(), which returns an object with provider-specific data which can be used to query their user information. For example, for a user authenticated with a Facebook credential in my app, this is the object returned by calling user.getIdentities():

{
"facebook":{
"userId":"Facebook:my-actual-user-id",
        "accessToken":"the-actual-access-token"
}
}

And for Twitter:

{
"twitter":{
"userId":"Twitter:my-actual-user-id",
"accessToken":"the-actual-access-token",
"accessTokenSecret":"the-actual-access-token-secret"
}
}

Microsoft:

{
"microsoft":{
"userId":"MicrosoftAccount:my-actual-user-id",
"accessToken":"the-actual-access-token"
}
}

Google:

{
"google":{
"userId":"Google:my-actual-user-id",
"accessToken":"the-actual-access-token"
}
}

Each of those objects has the information that we need to talk to the providers API. So let’s see how we can talk to their APIs to get more information about the user which has logged in to our application. For the examples in this post, I’ll simply store the user name alongside the item which is being inserted

Talking to the Facebook Graph API

To interact with the Facebook world, you can either use one of their native SDKs, or you can talk to their REST-based Graph API. To talk to it, all we need is a HTTP client, and we do have the nice request module which we can import (require) on our server scripts. To get the user information, we can send a request to https://graph.facebook.com/me, passing the access token as a query string parameter. The code below does that. It checks whether the user is logged in via Facebook; if so, it will send a request to the Graph API, passing the token stored in the user identities object. If everything goes right, it will parse the result (which is a JSON object), and retrieve the user name (from its “name” property) and store in the item being added to the table.

  1. function insert(item, user, request) {
  2.     item.UserName = "<unknown>"; // default
  3.     user.getIdentities({
  4.         success: function (identities) {
  5.             var req = require('request');
  6.             if (identities.facebook) {
  7.                 var fbAccessToken = identities.facebook.accessToken;
  8.                 var url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
  9.                 req(url, function (err, resp, body) {
  10.                     if (err || resp.statusCode !== 200) {
  11.                         console.error('Error sending data to FB Graph API: ', err);
  12.                         request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
  13.                     } else {
  14.                         try {
  15.                             var userData = JSON.parse(body);
  16.                             item.UserName = userData.name;
  17.                             request.execute();
  18.                         } catch (ex) {
  19.                             console.error('Error parsing response from FB Graph API: ', ex);
  20.                             request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
  21.                         }
  22.                     }
  23.                 });
  24.             } else {
  25.                 // Insert with default user name
  26.                 request.execute();
  27.             }
  28.         }
  29.     });
  30. }

With the access token you can also call other functions on the Graph API, depending on what the user allowed the application to access. But if all you want is the user name, there’s another way to get this information: the userId property of the User object, for users logged in via Facebook is in the format “Facebook:<graph unique id>”. You can use that as well, without needing the access token, to get the public information exposed by the user:

  1. function insert(item, user, request) {
  2.     item.UserName = "<unknown>"; // default
  3.     var providerId = user.userId.substring(user.userId.indexOf(':') + 1);
  4.     user.getIdentities({
  5.         success: function (identities) {
  6.             var req = require('request');
  7.             if (identities.facebook) {
  8.                 var url = 'https://graph.facebook.com/' + providerId;
  9.                 req(url, function (err, resp, body) {
  10.                     if (err || resp.statusCode !== 200) {
  11.                         console.error('Error sending data to FB Graph API: ', err);
  12.                         request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
  13.                     } else {
  14.                         try {
  15.                             var userData = JSON.parse(body);
  16.                             item.UserName = userData.name;
  17.                             request.execute();
  18.                         } catch (ex) {
  19.                             console.error('Error parsing response from FB Graph API: ', ex);
  20.                             request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
  21.                         }
  22.                     }
  23.                 });
  24.             } else {
  25.                 // Insert with default user name
  26.                 request.execute();
  27.             }
  28.         }
  29.     });
  30. }

The main advantage of this last method is that it can not only be used in the client-side as well.

  1. private async void btnFacebookLogin_Click_1(object sender, RoutedEventArgs e)
  2. {
  3.     await MobileService.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
  4.     var userId = MobileService.CurrentUser.UserId;
  5.     var facebookId = userId.Substring(userId.IndexOf(':') + 1);
  6.     var client = new HttpClient();
  7.     var fbUser = await client.GetAsync("https://graph.facebook.com/" + facebookId);
  8.     var response = await fbUser.Content.ReadAsStringAsync();
  9.     var jo = JsonObject.Parse(response);
  10.     var userName = jo["name"].GetString();
  11.     this.lblTitle.Text = "Multi-auth Blog: " + userName;
  12. }

That’s it for Facebook; let’s move on to another provider.

Talking to the Google API

The code for the Google API is fairly similar to the one for Facebook. To get user information, we send a request to https://www.googleapis.com/oauth2/v3/userinfo, again passing the access token as a query string parameter.

  1. function insert(item, user, request) {
  2.     item.UserName = "<unknown>"; // default
  3.     user.getIdentities({
  4.         success: function (identities) {
  5.             var req = require('request');
  6.             if (identities.google) {
  7.                 var googleAccessToken = identities.google.accessToken;
  8.                 var url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + googleAccessToken;
  9.                 req(url, function (err, resp, body) {
  10.                     if (err || resp.statusCode !== 200) {
  11.                         console.error('Error sending data to Google API: ', err);
  12.                         request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
  13.                     } else {
  14.                         try {
  15.                             var userData = JSON.parse(body);
  16.                             item.UserName = userData.name;
  17.                             request.execute();
  18.                         } catch (ex) {
  19.                             console.error('Error parsing response from Google API: ', ex);
  20.                             request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
  21.                         }
  22.                     }
  23.                 });
  24.             } else {
  25.                 // Insert with default user name
  26.                 request.execute();
  27.             }
  28.         }
  29.     });
  30. }

Notice that the code is so similar that we can just merge them into one:

  1. function insert(item, user, request) {
  2.     item.UserName = "<unknown>"; // default
  3.     user.getIdentities({
  4.         success: function (identities) {
  5.             var url;
  6.             if (identities.google) {
  7.                 var googleAccessToken = identities.google.accessToken;
  8.                 url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + googleAccessToken;
  9.             } else if (identities.facebook) {
  10.                 var fbAccessToken = identities.facebook.accessToken;
  11.                 url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
  12.             }
  13.  
  14.             if (url) {
  15.                 var requestCallback = function (err, resp, body) {
  16.                     if (err || resp.statusCode !== 200) {
  17.                         console.error('Error sending data to the provider: ', err);
  18.                         request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
  19.                     } else {
  20.                         try {
  21.                             var userData = JSON.parse(body);
  22.                             item.UserName = userData.name;
  23.                             request.execute();
  24.                         } catch (ex) {
  25.                             console.error('Error parsing response from the provider API: ', ex);
  26.                             request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
  27.                         }
  28.                     }
  29.                 }
  30.                 var req = require('request');
  31.                 var reqOptions = {
  32.                     uri: url,
  33.                     headers: { Accept: "application/json" }
  34.                 };
  35.                 req(reqOptions, requestCallback);
  36.             } else {
  37.                 // Insert with default user name
  38.                 request.execute();
  39.             }
  40.         }
  41.     });
  42. }

And with this generic framework we can add one more:

Talking to the Windows Live API

Very similar to the previous ones, just a different URL. Most of the code is the same, we just need to add a new else if branch:

  1. if (identities.google) {
  2.     var googleAccessToken = identities.google.accessToken;
  3.     url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + googleAccessToken;
  4. } else if (identities.facebook) {
  5.     var fbAccessToken = identities.facebook.accessToken;
  6.     url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
  7. } else if (identities.microsoft) {
  8.     var liveAccessToken = identities.microsoft.accessToken;
  9.     url = 'https://apis.live.net/v5.0/me/?method=GET&access_token=' + liveAccessToken;
  10. }

And the user name can be retrieved in the same way as the others – notice that this is true because all three providers seen so far return the user name in the “name” property, so we didn’t need to change the callback code.

Getting Twitter user data

Twitter is a little harder than the other providers, since it needs two things from the identity (access token and access token secret). It also needs to have the request signed, so we’ll use the oauth capabilities of the request module. For that, we need both the token and token secret which we get from the identities, but we also need the consumer key and consumer secret from the twitter app – the values which we had to enter in the ‘identities’ tab in the portal to enable Twitter auth. We could hardcode those values here, but we can actually get them from the process environment variables, using the ‘MS_TwitterConsumerKey’ and ‘MS_TwitterConsumerSecret’ values, as shown below:

  1. function insert(item, user, request) {
  2.     item.UserName = "<unknown>"; // default
  3.     user.getIdentities({
  4.         success: function (identities) {
  5.             var url = null;
  6.             var oauth = null;
  7.             if (identities.google) {
  8.                 var googleAccessToken = identities.google.accessToken;
  9.                 url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + googleAccessToken;
  10.             } else if (identities.facebook) {
  11.                 var fbAccessToken = identities.facebook.accessToken;
  12.                 url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
  13.             } else if (identities.microsoft) {
  14.                 var liveAccessToken = identities.microsoft.accessToken;
  15.                 url = 'https://apis.live.net/v5.0/me/?method=GET&access_token=' + liveAccessToken;
  16.             } else if (identities.twitter) {
  17.                 var userId = user.userId;
  18.                 var twitterId = userId.substring(userId.indexOf(':') + 1);
  19.                 url = 'https://api.twitter.com/1.1/users/show.json?user_id=' + twitterId;
  20.                 var consumerKey = process.env.MS_TwitterConsumerKey;
  21.                 var consumerSecret = process.env.MS_TwitterConsumerSecret;
  22.                 oauth = {
  23.                     consumer_key: consumerKey,
  24.                     consumer_secret: consumerSecret,
  25.                     token: identities.twitter.accessToken,
  26.                     token_secret: identities.twitter.accessTokenSecret
  27.                 };
  28.             }
  29.  
  30.             if (url) {
  31.                 var requestCallback = function (err, resp, body) {
  32.                     if (err || resp.statusCode !== 200) {
  33.                         console.error('Error sending data to the provider: ', err);
  34.                         request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
  35.                     } else {
  36.                         try {
  37.                             var userData = JSON.parse(body);
  38.                             item.UserName = userData.name;
  39.                             request.execute();
  40.                         } catch (ex) {
  41.                             console.error('Error parsing response from the provider API: ', ex);
  42.                             request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
  43.                         }
  44.                     }
  45.                 }
  46.                 var req = require('request');
  47.                 var reqOptions = {
  48.                     uri: url,
  49.                     headers: { Accept: "application/json" }
  50.                 };
  51.                 if (oauth) {
  52.                     reqOptions.oauth = oauth;
  53.                 }
  54.                 req(reqOptions, requestCallback);
  55.             } else {
  56.                 // Insert with default user name
  57.                 request.execute();
  58.             }
  59.         }
  60.     });
  61. }

And since the user name is also stored in the “name” property of the Twitter response, the callback doesn’t need to be modified.

Accessing provider APIs from the client

So far I’ve shown how you can get user information from the script, and some simplified version of it for the client side (for Facebook and Twitter). But what if we want the logic to access the provider APIs to live in the client, and just want to retrieve the access token which is stored in the server? Right now, there’s no clean way of doing that (no “non-CRUD operation” support on Azure Mobile Services), so what you can do is to create a “dummy” table that is just used for that purpose.

In the portal, create a new table for your application – for this example I’ll call it Identities, set the permissions for Insert / Delete and Update to “Only Scripts and Admins” (so that nobody will insert any data in this table), and for Read set to “Only Authenticated Users”

IdentitiesTable

Now in the Read script, return the response as requested by the caller, with the user identities stored in a field of the response. For the response: if a specific item was requested, return only one element; otherwise return a collection with only that element:

  1. function read(query, user, request) {
  2.     user.getIdentities({
  3.         success: function (identities) {
  4.             var result = {
  5.                 id: query.id,
  6.                 identities: identities
  7.             };
  8.             if (query.id) {
  9.                 request.respond(200, result);
  10.             } else {
  11.                 request.respond(200, [result]);
  12.             }
  13.         }
  14.     });
  15. }

And we can then get the identities on the client as a JsonObject by retrieving data from that “table”.

  1. var table = MobileService.GetTable("Identities");
  2. var response = await table.ReadAsync("");
  3. var identities = response.GetArray()[0].GetObject();

    

Notice that there’s no LookupAsync method on the “untyped” table, so the result is returned as an array; it’s possible that this will be added to the client SDK in the future, so we won’t need to get the object from the (single-element) array, receiving the object itself directly.

Wrapping up

The new multi-provider authentication support added in Azure Mobile Services made it quite easy to authenticate users to your mobile application, and it also gives you the power to access the provider APIs. If you have any comments or feedback, don’t hesitate to send either here on in the Azure Mobile Services forum.

Late update: since this post has been published, a new authentication provider has been added: Azure Active Directory (AAD). If you need to get user information about AAD, my colleague Matthew Henderson has written a post that talks about this scenario. Check it out at https://www.acupofcode.com/2014/01/accessing-aad-graph-information-from-azure-mobile-services/.