Expanded login scopes in Azure Mobile Services
A while back I wrote a post about getting more information about the logged in user in your mobile service, and this has been by far the most viewed post in the list I wrote about mobile services. By getting the identity object for the logged in user, the server scripts could talk to the authentication providers to get more information about the users, and with that have additional information (other than some id) about the users of their mobile applications. But that feature had some limitations, mainly related to the amount of information available from the providers to the user scripts. The issue was that the authentication scopes requested by the login flow was fixed (for example, the user’s e-mail was not available). With a feature we announced a couple of weeks back, this limitation is now removed. Notice that the feature is still in preview mode, so there’s not a lot of documentation out there, which is why I decided to write about it on this post.
The change made in the release 1.6.4247 of the Azure Mobile Services allows us to specify custom scopes which will be passed to the authentication providers when doing a server-side (i.e., web-based) authentication. That will, in turn, make the access token given by the getIdentities method in the users object more powerful, in a sense that the script can now use it to request additional information from the provider API.
Quick note before moving on: most of the for this blog post can be found in my blogsamples repository in GitHub, under AzureMobileServices/GettingUserInfoFromAuthProviders, so the code in the post will be simplified (i.e., no error handling, some code duplication) but you can find the full code in the repository.
Let’s start with Google, for example. By default, the authentication flow will give the app (via the access token) some basic information about the user (more specifically, the information from the https://www.googleapis.com/auth/userinfo.profile scope). So if a logged in user calls an API with the following code:
- exports.get = function (request, response) {
- request.user.getIdentities({
- success: function (identities) {
- var http = require('request');
- console.log('Identities: ', identities);
- var url = 'https://www.googleapis.com/oauth2/v3/userinfo' +
- '?access_token=' + identities.google.accessToken;
- var reqParams = { uri: url, headers: { Accept: 'application/json' } };
- http.get(reqParams, function (err, resp, body) {
- var userData = JSON.parse(body);
- response.send(200, userData);
- });
- }
- });
- };
They will retrieve a response similar to this one (which I got when I signed in with my Google account):
{
"sub": "my-google-id",
"name": "Carlos Figueira",
"given_name": "Carlos",
"family_name": "Figueira",
"profile": "https://plus.google.com/my-google-plus-profile-number",
"picture": "https://lh3.googleusercontent.com/some-path/some-other-path/yet-another-path/photo.jpg",
"gender": "male",
"locale": "en"
}
But the user e-mail isn’t one available, and there was no way for the application to request more information from the user. Let’s now request an additional scope from the Google login, by going to the configure tab in the portal, and set the ‘MS_GoogleScope’ application setting:
Now if I login again to the mobile service and invoke the same API, we’ll see that we have some additional information in the response:
{
"sub": "my-google-id",
"name": "Carlos Figueira",
"given_name": "Carlos",
"family_name": "Figueira",
"profile": "https://plus.google.com/my-google-plus-profile-number",
"picture": "https://lh3.googleusercontent.com/some-path/some-other-path/yet-another-path/photo.jpg",
"email": "my-email@my-provider.com",
"email_verified": true,
"gender": "male",
"locale": "en"
}
Microsoft
For two other providers, the idea is the same. Let’s take a look at Microsoft next. By default, the login requests only the ‘wl.basic’ scope. But if we want to get more information about the user, we can do it the same way. For a very similar API (which talks to the Live, instead of Google API), here’s the result of a request to the /me endpoint:
{
"id": "my-live-id",
"name": "Carlos Figueira",
"first_name": "Carlos",
"last_name": "Figueira",
"link": "https://profile.live.com/",
"gender": null,
"locale": "en_US",
"updated_time": "2013-12-10T16:03:43-08:00"
}
But if I set some additional scopes:
I’d get the additional information I requested after logging in again:
{
"id": "my-live-id",
"name": "Carlos Figueira",
"first_name": "Carlos",
"last_name": "Figueira",
"link": "https://profile.live.com/",
"gender": null,
"emails": {
"preferred": "carlosfigueirafilho@hotmail.com",
"account": "carlosfigueirafilho@hotmail.com",
"personal": null,
"business": null
},
"locale": "en_US",
"updated_time": "2013-12-10T16:03:43-08:00"
}
For Facebook is the same thing: we can ask for additional scopes (using the ‘MS_FacebookScope’ app setting) and the token we get to talk to the Facebook Graph API gives us more access to the additional resources. Along in the same lines of the previous examples, let’s use this API to get some information about our logged in user.
- exports.get = function (request, response) {
- request.user.getIdentities({
- success: function (identities) {
- var http = require('request');
- console.log('Identities: ', identities);
- var url = 'https://graph.facebook.com/me?fields=id,name,birthday,hometown,email&access_token=' +
- identities.facebook.accessToken;
- var reqParams = { uri: url, headers: { Accept: 'application/json' } };
- http.get(reqParams, function (err, resp, body) {
- var userData = JSON.parse(body);
- response.send(200, userData);
- });
- }
- });
- };
And if I call it without any custom scopes, I only get the users id and name:
{
"id": "my-facebook-id",
"name": "Carlos Figueira"
}
But if I request additional scopes for that provider:
I’ll be able to call the same API (after logging in again) and get that additional data.
{
"email": "my-email@my-provider.com",
"id": "my-facebook-id",
"name": "Carlos Figueira",
"birthday": "my-birthday",
"hometown": {
"id": "101883789854098",
"name": "Recife, Brazil"
}
}
Final notes
As a good general rule, only request the minimum information you need from the user. Many users don’t like giving out a lot of information to the apps they use, so they may simply give up using your app just because they’re being asked too much when logging in.
Also, you may have noticed that there’s no setting for requesting custom scopes in the Twitter authentication. For that provider, “scope” for the twitter API can be controlled by the twitter application itself, so there’s no need for the custom application setting.
Comments
Anonymous
April 07, 2014
Very useful post, thanks! Teaching students AMS authentication and getting extra user details to personalise mobile apps was challenging as no documentation on it, this post sorted that out. One thing I'd like to know is - does AMS authentication use ACS? There was a really nice library for windows phone 7 that supported most providers and was really easy to get extra user details, sadly it no longer works for WP8: www.nuget.org/.../Phone.Identity.AccessControl.BasePageThanks again!Anonymous
April 28, 2014
I have been looking for a way to do this same thing in the Azure Mobile Services .net Preview and have not found a way to extend the scope in that type of azure mobile service yet. Is there something comparable I can do there yet?Anonymous
May 13, 2014
@Derek, Azure Mobile Services currently doesn't support ACS, but it does support Azure Active Directory (this feature was added a couple of months back).@Jeff, this is currently not supported in the .NET backend, but it's in our backlog of issues to fix in that runtime.Anonymous
June 15, 2014
Hi Carlos,I'm doing a bay.net talk on Wednesday on how I integrated Social signon to Silicon Valley Code Camp (www.meetup.com/.../185540132) and I want to talk about how to integrate explanded login info. I'm only interested in talking about non windows mobile html/javascript. It seems that I need to pass secret stuff around to get that info. Is that true? Any example of how to do it? I looked at your github project and the html/js looked it it was strictly for windows mobile.Anonymous
June 24, 2014
Carlos is this now available in the .Net Preview..I see an app settings section under the config screenIf so, would I add this to the value field: publish_actions, user_friends, public_profile...not sure if I need commas to notBobAnonymous
July 08, 2014
Hi Carlos,I tried this sample code but i am unable to get email id and other stuff. I suppose we need to set scoped permission for that. As i read on this blog social.msdn.microsoft.com/.../set-scope-and-permissions-for-windows-azure-mobile-services , it says we cannot set scoped permission when using azure mobile service. Is it still valid or there is some way to set permissions to access details like email.Anonymous
August 06, 2014
Carlos,This is a great article. Thank you!I took your post and created a new shared script where my intent is to take the currently logged in user and retrieve their email address from the Google service. I first look in a Users table to see if they have a record in it. If not, I grab the info from the Google API and then store the information in the Users table. Ultimately, my function returns the email address. The reason for this is because we associate users to records by using the email address they use to log in to Google (we know their email addresses, not the funky Google id).However, I'm receiving an error when calling the shared script. I've posted my error on stackoverflow (stackoverflow.com/.../azure-mobile-services-using-shared-script-to-resolve-google-user-to-their-email). Could you please take a look and point me to where I might be doing something wrong?Thanks!Anonymous
August 13, 2014
Hi @CarlosFigueiraI have problems with MS_MicrosoftScope parameter:I set value 'wl.signin wl.basic wl.emails' to MS_MicrosoftScope in my mobile service configure tabI call https://<my_mobile_service_url>/login/microsoftaccount in my web browser redirect to https://login.live.com/... where scope is 'wl.basic' other parameters in login url is correct. login performed is succeedAnonymous
August 13, 2014
@denis, is your service using the node.js or the .NET backend? Currently this feature is only available in the former. It should be added to the latter soon.Anonymous
August 14, 2014
@CarlosFigueira thanks for the answerI using .net.Anonymous
August 28, 2014
The comment has been removedAnonymous
September 21, 2014
Any time estimates to this feature will be made available for .NET as well.Thanks,Anonymous
September 21, 2014
And in the meantime is there any workaround for it?Anonymous
October 01, 2014
@Attiqe, this support should be coming this week, possibly even today. We'll announce it in our blog when it's live for all users.Anonymous
December 01, 2014
Is it live already?Anonymous
February 05, 2015
Hi,I follow the above suggestion and write the following code in my Controller to access Microsoft Account info.But it always returns "UnAuthorized".Could someone help me with this? thanks. var serviceUser = this.User as ServiceUser; var identities = await serviceUser.GetIdentitiesAsync(); var account = identities.OfType<MicrosoftAccountCredentials>().FirstOrDefault(); // the AccessToken is successfully retrieved. Services.Log.Info("AccessToken - " + account.AccessToken); var url = "apis.live.net/.../me + accessToken; var c = new HttpClient(); var req = new HttpRequestMessage(HttpMethod.Get, url); 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 { // it always came to here with Status Code UnAuthorized. providerResponse = "Error: " + resp.StatusCode; } Services.Log.Info("providerResponse - " + providerResponse);Anonymous
February 05, 2015
Resolved.I follow this and resolved.azure.microsoft.com/.../custom-login-scopes-single-sign-on-new-asp-net-web-api-updates-to-the-azure-mobile-services-net-backendThanks, Carlos!