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.
- function insert(item, user, request) {
- item.UserName = "<unknown>"; // default
- user.getIdentities({
- success: function (identities) {
- var req = require('request');
- if (identities.facebook) {
- var fbAccessToken = identities.facebook.accessToken;
- var url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
- req(url, function (err, resp, body) {
- if (err || resp.statusCode !== 200) {
- console.error('Error sending data to FB Graph API: ', err);
- request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
- } else {
- try {
- var userData = JSON.parse(body);
- item.UserName = userData.name;
- request.execute();
- } catch (ex) {
- console.error('Error parsing response from FB Graph API: ', ex);
- request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
- }
- }
- });
- } else {
- // Insert with default user name
- request.execute();
- }
- }
- });
- }
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:
- function insert(item, user, request) {
- item.UserName = "<unknown>"; // default
- var providerId = user.userId.substring(user.userId.indexOf(':') + 1);
- user.getIdentities({
- success: function (identities) {
- var req = require('request');
- if (identities.facebook) {
- var url = 'https://graph.facebook.com/' + providerId;
- req(url, function (err, resp, body) {
- if (err || resp.statusCode !== 200) {
- console.error('Error sending data to FB Graph API: ', err);
- request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
- } else {
- try {
- var userData = JSON.parse(body);
- item.UserName = userData.name;
- request.execute();
- } catch (ex) {
- console.error('Error parsing response from FB Graph API: ', ex);
- request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
- }
- }
- });
- } else {
- // Insert with default user name
- request.execute();
- }
- }
- });
- }
The main advantage of this last method is that it can not only be used in the client-side as well.
- private async void btnFacebookLogin_Click_1(object sender, RoutedEventArgs e)
- {
- await MobileService.LoginAsync(MobileServiceAuthenticationProvider.Facebook);
- var userId = MobileService.CurrentUser.UserId;
- var facebookId = userId.Substring(userId.IndexOf(':') + 1);
- var client = new HttpClient();
- var fbUser = await client.GetAsync("https://graph.facebook.com/" + facebookId);
- var response = await fbUser.Content.ReadAsStringAsync();
- var jo = JsonObject.Parse(response);
- var userName = jo["name"].GetString();
- this.lblTitle.Text = "Multi-auth Blog: " + userName;
- }
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.
- function insert(item, user, request) {
- item.UserName = "<unknown>"; // default
- user.getIdentities({
- success: function (identities) {
- var req = require('request');
- if (identities.google) {
- var googleAccessToken = identities.google.accessToken;
- var url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + googleAccessToken;
- req(url, function (err, resp, body) {
- if (err || resp.statusCode !== 200) {
- console.error('Error sending data to Google API: ', err);
- request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
- } else {
- try {
- var userData = JSON.parse(body);
- item.UserName = userData.name;
- request.execute();
- } catch (ex) {
- console.error('Error parsing response from Google API: ', ex);
- request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
- }
- }
- });
- } else {
- // Insert with default user name
- request.execute();
- }
- }
- });
- }
Notice that the code is so similar that we can just merge them into one:
- function insert(item, user, request) {
- item.UserName = "<unknown>"; // default
- user.getIdentities({
- success: function (identities) {
- var url;
- if (identities.google) {
- var googleAccessToken = identities.google.accessToken;
- url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + googleAccessToken;
- } else if (identities.facebook) {
- var fbAccessToken = identities.facebook.accessToken;
- url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
- }
- if (url) {
- var requestCallback = function (err, resp, body) {
- if (err || resp.statusCode !== 200) {
- console.error('Error sending data to the provider: ', err);
- request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
- } else {
- try {
- var userData = JSON.parse(body);
- item.UserName = userData.name;
- request.execute();
- } catch (ex) {
- console.error('Error parsing response from the provider API: ', ex);
- request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
- }
- }
- }
- var req = require('request');
- var reqOptions = {
- uri: url,
- headers: { Accept: "application/json" }
- };
- req(reqOptions, requestCallback);
- } else {
- // Insert with default user name
- request.execute();
- }
- }
- });
- }
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:
- if (identities.google) {
- var googleAccessToken = identities.google.accessToken;
- url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + googleAccessToken;
- } else if (identities.facebook) {
- var fbAccessToken = identities.facebook.accessToken;
- url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
- } else if (identities.microsoft) {
- var liveAccessToken = identities.microsoft.accessToken;
- url = 'https://apis.live.net/v5.0/me/?method=GET&access_token=' + liveAccessToken;
- }
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:
- function insert(item, user, request) {
- item.UserName = "<unknown>"; // default
- user.getIdentities({
- success: function (identities) {
- var url = null;
- var oauth = null;
- if (identities.google) {
- var googleAccessToken = identities.google.accessToken;
- url = 'https://www.googleapis.com/oauth2/v3/userinfo?access_token=' + googleAccessToken;
- } else if (identities.facebook) {
- var fbAccessToken = identities.facebook.accessToken;
- url = 'https://graph.facebook.com/me?access_token=' + fbAccessToken;
- } else if (identities.microsoft) {
- var liveAccessToken = identities.microsoft.accessToken;
- url = 'https://apis.live.net/v5.0/me/?method=GET&access_token=' + liveAccessToken;
- } else if (identities.twitter) {
- var userId = user.userId;
- var twitterId = userId.substring(userId.indexOf(':') + 1);
- url = 'https://api.twitter.com/1.1/users/show.json?user_id=' + twitterId;
- var consumerKey = process.env.MS_TwitterConsumerKey;
- var consumerSecret = process.env.MS_TwitterConsumerSecret;
- oauth = {
- consumer_key: consumerKey,
- consumer_secret: consumerSecret,
- token: identities.twitter.accessToken,
- token_secret: identities.twitter.accessTokenSecret
- };
- }
- if (url) {
- var requestCallback = function (err, resp, body) {
- if (err || resp.statusCode !== 200) {
- console.error('Error sending data to the provider: ', err);
- request.respond(statusCodes.INTERNAL_SERVER_ERROR, body);
- } else {
- try {
- var userData = JSON.parse(body);
- item.UserName = userData.name;
- request.execute();
- } catch (ex) {
- console.error('Error parsing response from the provider API: ', ex);
- request.respond(statusCodes.INTERNAL_SERVER_ERROR, ex);
- }
- }
- }
- var req = require('request');
- var reqOptions = {
- uri: url,
- headers: { Accept: "application/json" }
- };
- if (oauth) {
- reqOptions.oauth = oauth;
- }
- req(reqOptions, requestCallback);
- } else {
- // Insert with default user name
- request.execute();
- }
- }
- });
- }
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”
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:
- function read(query, user, request) {
- user.getIdentities({
- success: function (identities) {
- var result = {
- id: query.id,
- identities: identities
- };
- if (query.id) {
- request.respond(200, result);
- } else {
- request.respond(200, [result]);
- }
- }
- });
- }
And we can then get the identities on the client as a JsonObject by retrieving data from that “table”.
- var table = MobileService.GetTable("Identities");
- var response = await table.ReadAsync("");
- 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/.
Comments
- Anonymous
November 08, 2012
How can I get the email of the authenticated user when using google as the identity provider?I can only the basic profile information (name, gender etc) but I need the email... - Anonymous
November 09, 2012
Hi Björn, currently it's not possible to get that information from the access token. I have raised an issue with the product team to support additional login scopes, I'll update this comment (and the thread in the forum) when this happens.Thanks! - Anonymous
February 27, 2013
The comment has been removed - Anonymous
March 11, 2013
Thanks for a great post! Keep up the good work! - Anonymous
March 21, 2013
Hi @thakur.hansraj, is the user logged when the call to insert is made? Unless the user is logged in to the Azure Mobile Service, the user object won't have any information about the provider identities. - Anonymous
April 06, 2013
Another super useful post! This should be a part of standard documentation, imo :) - Anonymous
April 11, 2013
How can we add Roles to this, so that I can add a Member role and an Administrator role? Would you please do a post about Roles with ACS/Live ID? Or do you know which documentation I should read, as I cannot find anything on Roles with Azure Mobile Services and Store apps. - Anonymous
April 16, 2013
thx a lot. Great tutorial :) lovin azure - Anonymous
April 18, 2013
Carlos - This information is great and I have been using it in my Mobile Service-powered app for some time. But I recently ran into an issue that I was hoping Mobile Services could help solve. The 'accessToken' values available off the user.identities object can expire. When they do, calls to the service provider using them fail. Most of the providers allow you to renew the tokens, but sometimes they require additional information. For instance, Microsoft/Live requires a 'renewal token' to be passed, but that value is not accessible from Mobile Services. Neither is the 'expiration date' that indicates when the token will expire. If you have any advice on a good way to handle this, I would love to hear it. If not, this seems like a natural next step for the Mobile Services authentication service to handle. - Anonymous
May 08, 2013
Thanks, just what I need - Anonymous
June 02, 2013
Thank for your post!!!It helped me to choose the right way in my career! - Anonymous
July 09, 2013
I don't see how this is useful without being able to get the actual username (the email address) that was used to sign in. - Anonymous
July 13, 2013
Hi, I think the Twitter instructions to retrieve the users name are now broken as the API 1.0 has been updated. I think it now needs all requests to be authenticated and I can't figure out how to do that. The URLapi.twitter.com/.../show.json gives this error message:{"errors":[{"message":"Bad Authentication data","code":215}]} - Anonymous
September 25, 2013
Excellent. I did get the facebook part to work nicely and I got the email address as I wanted.However, for google, I dont get the email address, only name, DOB, etc.How do I modify the above to get the email address from google? - Anonymous
October 30, 2013
Hi Carlos,Thanks for your post. Quick question. What is the best practice to prevent the userid tamper, especially if that is used to relate records at the database? Is there a way to discover the userid at the server by just sending the auth token to the server ? - Anonymous
November 14, 2013
Thanks Carlos. I know it's been a while since this discussion was created, but has there been any update allowing us to retrieve the user's e-mail from the access token? - Anonymous
November 27, 2013
@Jon, there is a new feature released last week which allows you to specify different login scopes, which allows you to get the user e-mail. It was announced in the Azure Mobile blog at blogs.msdn.com/.../what-s-new-in-azure-mobile-services-1-6-4247.aspx. I'll write a post about this in a few weeks (I have a couple in the pipeline to come before that)@James, I updated this post with information about the authentication for talking to the the twitter API@Magnus, as I mentioned above, you can now request additional scopes for Google which will give you that information.@Arvind, the user id is encoded in the client request, and that request is signed with the mobile service master key. So unless that key has leaked, the user id should be considered secure.@Matt, yes, it was released last week - see my comment above. - Anonymous
February 19, 2014
Hy Carlos,I am trying to login the user "sillently" the second time he enter the app. I use the the LoginAsync(String IdentityProvider, jToken) and I get the followind error for google : The POST Google login request must contain both code and id_token in the body of the request, and for twitter : POST of Twitter token is not supported.". Any suggestions? Thanks - Anonymous
May 12, 2014
If I login using facebook, how can i later post to this users wall? - Anonymous
May 13, 2014
@Demian, the google client flow (when you use a token obtained from a Google SDK, such as the Google Play Services, to log in to the Mobile Service) is currently broken. See the thread at github.com/.../185 for updates on the issue. When it's fixed we'll also announce in the team blog at blogs.msdn.com/azuremobile.@george, as long as you have the appropriate permissions (see blogs.msdn.com/.../expanded-login-scopes-in-azure-mobile-services.aspx), that should work, although I haven't tried. - Anonymous
May 20, 2014
Hi Carlos,I'm trying to implement this but am confused about where to put this insert function. Do I need to create a separate table (ie "userinfo") to hold the data?Thanks! - Anonymous
May 21, 2014
@Christian, you don't need to create a separate table, you can use a custom API instead (unless you want to cache the data so that if you need it later you don't need to query the auth provider again). When I wrote this post there was no support for custom APIs on Azure Mobile Services, so I used a table script instead. - Anonymous
July 27, 2014
Hi, any idea how to achieve this with .NET backend? - Anonymous
August 08, 2014
The ApiController (which is an ancestor class of any controller you have in the .NET backend, including TableController<T>) has a property called User. If the request comes from a user which is logged in to the mobile service, you can cast that to a ServiceUser instance. That ServiceUser exposes a GetIdentitiesAsync method, which returns a list of credentials associated with the user (similar to the getIdentities in the node.js runtime). Once you get those credentials, you can scope it down to the type you want (in the example below, facebook), and get the access token required to talk to the authentication provider from there.The code below shows how you could get information from a FB user. For other providers the code is fairly similar. var serviceUser = this.User as ServiceUser; if (serviceUser != null) { var identities = await serviceUser.GetIdentitiesAsync(); var fb = identities.OfType<FacebookCredentials>().FirstOrDefault(); if (fb != null) { var fbAccessToken = fb.AccessToken; var fbUrl = "graph.facebook.com/me + fbAccessToken; var c = new HttpClient(); var req = new HttpRequestMessage(HttpMethod.Get, fbUrl); req.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); var resp = await c.SendAsync(req); string providerResponse; if (resp.StatusCode == HttpStatusCode.OK) { providerResponse = await resp.Content.ReadAsStringAsync(); } else { providerResponse = "Error: " + resp.StatusCode; } // use the providerResponse as you wish. Here I'm adding it to a property in the item returned to the client current.Identities = providerResponse; } } - Anonymous
August 20, 2014
It is working, thank you! I wonder where could I read about this, could you point me to some documentation regarding this subject? - Anonymous
August 23, 2014
HiCan someone please help to understand why I am getting an error "Error: The Facebook Graph API request failed with HTTP status code 400". I just used a WINJS.Promise..etc in the client side. On the azure mobile service table I didn't code anything except the facebook code shown in this blog at the beginning as is. I tried to run the app and got the same error when in gave credentials for facebook and clicked on login. This error was thrown even without that azure mobile service table script stated above. Please help. I am a student working solely on a project. Thanks in advance. - Anonymous
August 25, 2014
HiVery new to this area as most of us are.Building a mobile app with a Mobile Service back-end. It will be using social media credentials to log into Media Service using server-side auth flow as we cannot get the client-side to work for FB.Once the user is logged in how can I get information about the user from its provider (facebook) using .NET rather than the scripts provided above.If there is no alternative to using the scripts - is there an example how I can call the scripts from the .NET?Regards - Anonymous
September 05, 2014
Hi,I have read the article it helped me a lot. But WAMS doesn't pick process environment variables in editor's intellisense.Any thoughts?Thanks, - Anonymous
September 05, 2014
If you can ignore the squiggly lines in the editor, then you're good - the runtime is fine with those. If you really don't like those, then you can access the settings via the config. If you're on a custom API, you can use `request.service.config.appSettings.<app setting name>. If you're in a table script, you can require the 'mobileservice-config' module and use it from there. See the post at blogs.msdn.com/.../application-settings-in-azure-mobile-services.aspx for more info. - Anonymous
November 27, 2014
The comment has been removed - Anonymous
December 14, 2014
Carlos,Anything later than this on getting extra info from the identity providers? I remember when I tried this there were issues but can't remember the details. Also, any other providers coming? (like dropbox or linkedin)?Happy Holidays! - Anonymous
February 15, 2015
How do you get provider specific information whe the provider is a custom LoginProvider. GetIdentities is always empty for me?! - Anonymous
April 15, 2015
Hi,MS released a new product: MobileApp, which is a kind of a variant of the "old" Azure Media Services, but a couple of things changed. For example the user management. In the new back-end the UserId doesn't contain the provider name and it seems the social access token is also missing. The services is in preview, so not so much documentation available yet. Do you have any idea how to get user social info in case of Azure Mobile App back-end?Thanks,Daniel - Anonymous
April 16, 2015
For Mobile Apps, you should make sure to install the App Service extension NuGet: www.nuget.org/.../Microsoft.Azure.Mobile.Server.AppServiceThis exposes a getIdentityAsync extension method on the ServiceUser object, and all of the above information is available through there. This is like the Enhanced users feature that Carlos blogged about before: blogs.msdn.com/.../enhanced-users-feature-in-azure-mobile-services.aspx - Anonymous
April 17, 2015
Thank you Matthew!One more thing that I would like to ask: currently I'm trying to get info from each provider and finally test the AccessToken property, because don't know which identity provider is the correct one:FacebookCredentials facebookCredentials = await serviceUser.GetIdentityAsync<FacebookCredentials>();GoogleCredentials googleCredentials = await serviceUser.GetIdentityAsync<GoogleCredentials>();TwitterCredentials twitterCredentials = await serviceUser.GetIdentityAsync<TwitterCredentials>();if (!String.IsNullOrEmpty(facebookCredentials.AccessToken)) { doSomething(); }else if (!string.IsNullOrEmpty(googleCredentials.AccessToken)) { doSomething(); }else if (!string.IsNullOrEmpty(twitterCredentials.AccessToken)) { doSomething(); }Is there a more sophisticated method to figure out the user's identity provider? - Anonymous
April 17, 2015
The design may be changing a bit as the preview goes on. Nothing official yet, but I suspect it will look more like the Mobile Services getIdentitiesAsync() method, which returned a Collection<ProviderCredentials> which was easier to check and work with. Appreciate the feedback!