1

I need to get the user's extra information from social accounts like Facebook and Google+. When I first read about Azure Mobile Services I thought it to be the holy grail of social authentication. Well, after a full week of hair pulling I'm starting to reconsider my first impression. It does authenticate as easily as it could possibly do. I configured Google+ and FB to work with Azure, configured Azure to use the key/secret from each provider and it all just worked. I was able to login perfectly. The problem started when I tried to get information from the logged user, which I honestly think is basic!

Azure Mobile Services returns the UserId and a Token that you can not use to request the extra info on the selected provider. So even if I were to create a second request using FB's graph API for instance, that wouldn't work (I've tried!). That token is Azure's own token. So I found out from several Carlos Figueira (SE at Azure) posts that I should customize my Azure script, make a request to Azure and then I'd be able to get it working.

I've also read several posts from Carlos Figueira on how to implement that extra functionality and even though that was not what I was looking for (customizing the server) I decided to work with that. But my return type is a MobileServiceUser and that type only has 2 properties: UserId and MobileServiceAuthenticationToken. So even after adding the server script from Carlos I couldn't retrieve the extra information from my Xamarin App.

I've read a lot of things, researched a lot and couldn't find an answer =/ By the way this is not the answer: How to get user name, email, etc. from MobileServiceUser?

Did anyone manage to make it work?

PS: I'm not posting any code here because it's working. If you think checking some part of my code would help decipher the problem just let me know.

Thanks in advance!

EDIT:

Script

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();
            }
        }
    });
}
Community
  • 1
  • 1
eestein
  • 4,914
  • 8
  • 54
  • 93

2 Answers2

2

You're talking about the token on the client side correct? That token is specific only to the client. If you're using Server Side flow, the server is the only one with that token. If you want to send that to the client, you need to do that via a custom API you create.

This class you're talking about does only contain those two properties. But on your server side, your ServiceUser can access the different identity provider tokens in order to speak to those servers APIs. Your linked post is correct in how you access the token, you're mistaken on where you can access that token, it's only on the server side (if you use the server directed login flow).

Chris Anderson
  • 8,305
  • 2
  • 29
  • 37
  • I think you misread something... **Your linked post is correct in how you access the token** I know, that's why I said `By the way this is not the answer`. **you're mistaken on where you can access that token, it's only on the server side** I know, that's why I said `So even if I were to create a second request using FB's graph API for instance, that wouldn't work (I've tried!). That token is Azure's own token`. Even if I use the server login flow (which I said in my post, I have) I still can't get that info because the class (yes, the one you linked) doesn't allow me to. Thanks for your time. – eestein Jul 04 '15 at 01:01
  • So the server has the actual IDP token. So on the server side, you can use the `ServiceUser` identity to grab the proper token for accessing the IDP's API. Here's another example where they were a bit confused by this and figured it out. http://stackoverflow.com/questions/25463125/azure-mobile-service-with-facebook-auth-get-user-info The class I linked and the class you were trying to get the token out of does NOT have the IDP token, it only has the MobileService ID. You'll likely want to just fetch the data on the server side using the method in the linked posts. – Chris Anderson Jul 04 '15 at 04:07
  • Ok, I was trying to tell you I know and I've tried that. Not saying I did it right, but I did try that. Also my backend is JS not .NET, so I don't know if the solution would be the same, would it? Since you work @MS you are probably familiar with Carlos Figueira... as I said in my post I followed his tutorials for the JS backend, but again, not saying the mistake is not on my part... just saying it doesn't look like the information for that current scenario is easy to reach. If you could provide extra information on that, I'd appreciate it. – eestein Jul 04 '15 at 12:13
  • Yeah same notion for the JS backend. Would you consider sharing your backend code where you're trying to access the user's IDP token? I can try to eyeball if something looks misconfigured. – Chris Anderson Jul 04 '15 at 14:20
  • Sure, just check my updated question. But as I said, the problem is that even if I manage to return that information, the class MobileServiceUser won't allow me to "see" those properties because it only has two properties. Thanks. – eestein Jul 04 '15 at 14:31
  • Yes. That will always be the case. You need a customer API or a "table", as Carlos suggests at the end of his Blog, which returns that information to you. The MobileServiceUser will never expose that information directly. You must expose this yourself. – Chris Anderson Jul 04 '15 at 14:41
  • Ok, after that code I showed you, what else do I need to do? Thanks. – eestein Jul 04 '15 at 19:07
  • Use the last section of Carlos' blog. http://blogs.msdn.com/b/carlosfigueira/archive/2012/10/25/getting-user-information-on-azure-mobile-services.aspx You basically create an identities table which get synced down to your device. – Chris Anderson Jul 04 '15 at 19:30
  • Chris, I appreciate your time and as it seems there's no better solution/answer right now I'm deciding if I'm going with that excessive extra work or just moving to a regular oAuth implementation. I still think you guys should've implemented this by default! I understand the security concerns involved in transactions using the access token, but it shouldn't be this hard to get that information. Thanks again. – eestein Jul 05 '15 at 14:49
  • Thanks for the feedback. We're working to make the Auth experience better for our next iteration. It sounds like client-side auth might be more what you expect since you want the token on the client-side. I'd suggest looking at that if you don't want to create a Server side API to fetch the identities. Let me know if you have any other issues: zumocn@microsoft.com – Chris Anderson Jul 05 '15 at 17:25
  • Chris, I wouldn't want nor need the token If Azure's solution would accept scope request like those MS_*** scope variables and return that data to the client, like email. I think that's pretty basic info to provide, even if it was by request as I mentioned by creating the MS_*** variables at Azure. You should really consider that if it's not in your current plans. The need is clear, just check all these fellow devs asking how to get that information from azure... Thanks. – eestein Jul 06 '15 at 14:22
  • Unfortunately, things have changed slightly from Azure Mobile Service to Azure Mobile Apps. I had the a similar script to the original one posted on this thread that worked with Mobile Services to get the profile of the user for each authentication method. The process is similar with Azure Mobile Apps in the sense that you authenticate via Google, Twitter, etc. on the client side then call a Custom API in the Mobile App to execute retrieving the actual profile information. However, the documentation is lacking in where some of the environment variables are. – James Q Quick Feb 03 '16 at 01:52
  • Go ahead and create a question if things aren't clear in Mobile Apps. Our team monitors for questions and we'll do our best to help. – Chris Anderson Feb 05 '16 at 03:25
0

Here is the custom API Script I had working in Mobile Services to return the profile of the logged in user. I am working on updating to Mobile Apps as some environment variables appear to have changed. Would love to know if anyone has gotten it to work with Mobile Apps.

exports.get = function (request, response) {
var user = request.user;
user.getIdentities({
    success: function (identities) {
        var req = require('request');
        var url = null;
        var oauth = null;
        var userId = user.userId.split(':')[1];
        console.log('Identities: ', identities);
        if (identities.facebook) {
            url = 'https://graph.facebook.com/me?access_token=' +
                identities.facebook.accessToken;
        } else if (identities.google) {
            url = 'https://www.googleapis.com/oauth2/v3/userinfo' +
                '?access_token=' + identities.google.accessToken;
        } else if (identities.microsoft) {
            url = 'https://apis.live.net/v5.0/me?access_token=' +
                identities.microsoft.accessToken;
        } else if (identities.twitter) {
            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
            };
            url = 'https://api.twitter.com/1.1/users/show.json?' +
                'user_id=' + userId + '&include_entities=false';
        } else {
            response.send(500, { error: 'No known identities' });
            return;
        }

        if (url) {
            var reqParams = { uri: url, headers: { Accept: 'application/json' } };
            if (oauth) {
                reqParams.oauth = oauth;
            }
            req.get(reqParams, function (err, resp, body) {
                if (err) {
                    console.error('Error calling provider: ', err);
                    response.send(500, { error: 'Error calling provider' });
                    return;
                }

                if (resp.statusCode !== 200) {
                    console.error('Provider call did not return success: ', resp.statusCode);
                    response.send(500, { error: 'Provider call did not return success: ' + resp.statusCode });
                    return;
                }

                try {
                    var userData = JSON.parse(body);
                    response.send(200, userData);
                } catch (ex) {
                    console.error('Error parsing response: ', ex);
                    response.send(500, { error: ex });
                }
            });
        } else {
            response.send(500, { error: 'Not implemented yet', env: process.env });
        }
    }
});

};

James Q Quick
  • 429
  • 3
  • 5