Authenticating mobile Facebook users

Up until now, I have been using Facebook C# SDK to handle authentication of users for Am I Interesting. However, when Facebook released separate handling of mobile users – which is welcome – the SDK did not support that. The Facebook error message shown to the users was:

The mobile version of the app “Am I Interesting” is unavailable because it is misconfigured. It appears to be caught in a redirect loop.

The underlying issue is that all users, no matter how they access the application, get redirected to the same URL when using the SDK, in my case to apps.facebook.com/amiinteresting, and that does not work for mobile users. For mobile users, we have to redirect them outside of Facebook’s IFrame.

My authentication goal:
Get a Facebook Access Token and Facebook User Id for all users who login

What I need as far as URLs go:

  • “Regular” users should be redirected to apps.facebook.com/amiinteresting (IFrame within Facebook)
  • Mobile users should be redirected to www.amiinteresting.net (outside Facebook’s frame)

For the solution described below, I followed Facebook’s developer documentation about authentication at:
http://developers.facebook.com/docs/authentication/

For the first login step, I use the following code:

        public ActionResult Login()
        {
            var redirectUrl = string.Format("https://www.facebook.com/dialog/oauth?client_id={0}&scope={1}&redirect_uri={2}",
            _configurationHelper.FacebookAppId,
            string.Join(",", FacebookPermissions.RequiredPermissions),
            _configurationHelper.FacebookRedirectUrl);
            var html = string.Format("<html><body><script type=\"text/javascript\">window.top.location='{0}';</script></body></html>", redirectUrl);
            return Content(html, "text/html");
        }

The Facebook Redirect Url configuration setting above is always http://www.amiinteresting.net. Facebook takes care of instead redirecting to apps.facebook.com/amiinteresting if it’s not a mobile user.

After fixing the redirect problem, I noticed that:

  • When a “regular” user arrives to the application at apps.facebook.com within an IFrame, Facebook posts what’s called a signed_request that can be decoded and contains an access token (which I need), the User ID, and some other nice info (described here).
  • When a mobile user arrives to the application outside Facebook’s frame, I don’t get a signed_request, but rather a “code” in the querystring that can be used to query Facebook together with the application secret to get the access_token and other info as needed.

So – I needed two separate ways to handle this, and implemented them in a method that I call CheckNewFacebookLogin (supporting methods further down):

        public bool CheckNewFacebookLogin()
        {
            var codeInUrl = _currentHttpContext.QueryString["code"];
            var signedRequest = _currentHttpContext.Form["signed_request"];

            if (codeInUrl == null && signedRequest == null)
            {
                return false;
            }

            if (signedRequest != null)
            {

                // Signed Request
                var parametersInSignedRequest = ExtractDataFromRequest(signedRequest);
                var accessToken = parametersInSignedRequest["oauth_token"].ToString();
                var userId = Convert.ToInt64(parametersInSignedRequest["user_id"]);
                if (_sessionVariables.AccessToken != null && _sessionVariables.AccessToken == accessToken)
                {
                    return false; // User already authenticated and no new user logged in
                }
                _sessionVariables.InvalidateSession(); // New app login or new user accessed application
                _sessionVariables.AccessToken = accessToken;
                _sessionVariables.UserInfo = new PersonEntity
                                                 {
                                                     UserId = userId
                                                 };
            }
            else
            {

                // Code in URL
                if (_sessionVariables.FacebookCode != null && _sessionVariables.FacebookCode == codeInUrl)
                {
                    return false; // User already authenticated and no new user logged in
                }
                _sessionVariables.InvalidateSession(); // New app login or new user accessed application
                _sessionVariables.FacebookCode = codeInUrl;
            }

            return true;
        }

If the method returns true = a new user logged in, I display a landing/waiting page and in parallell call this CompleteLogin method (simplified):

        public void CompleteLogin()
        {
            // Get token & userId
            if (_sessionVariables.FacebookCode != null)
            {
                var accessToken = GetFacebookAccessToken(_sessionVariables.FacebookCode);
                _sessionVariables.AccessToken = accessToken;

                var userId = _facebookService.GetUserIdFromFacebookToken(_sessionVariables.AccessToken);
                _sessionVariables.UserInfo = new PersonEntity
                {
                    UserId = userId
                };
            }
        }

        private string GetFacebookAccessToken(string code)
        {
            var requestUrl = string.Format("https://graph.facebook.com/oauth/access_token?client_id={0}&client_secret={1}&code={2}&redirect_uri={3}",
                _configurationHelper.FacebookAppId,
                _configurationHelper.FacebookAppSecret,
                code,
                _configurationHelper.FacebookRedirectUrl);
            var response = _facebookRequest.GetTextResponse(new Uri(requestUrl));
            var parameters = response.Split('&');
            foreach (var parameter in parameters)
            {
                var key = parameter.Split('=')[0];
                var value = parameter.Split('=')[1];
                if (key == "access_token")
                {
                    return value;
                }
            }
            return null;
        }

Supporting methods:

        private static Dictionary<string, object> ExtractDataFromRequest(string signedRequest)
        {
            var jsonEncodedObject = signedRequest.Split('.')[1];
            var json = Base64_Url_Decode(jsonEncodedObject);
            var parameters = SerializeHelpers.DeserializeFromJson(json);
            var parameterDictionary = new Dictionary<string, object>();
            foreach (var parameter in parameters)
            {
                parameterDictionary.Add(parameter.Key, parameter.Value);
            }
            return parameterDictionary;
        }

        public static string Base64_Url_Decode(string input)
        {
            // From http://www.hazardbrick.com/post/2011/04/05/Facebook-Login-Registration-signed_request-Base64-problems-in-ASPNet-C.aspx
            string fixedString;
            var fixedDashString = input.Replace('-', '+');
            var fixedUnderscoreString = fixedDashString.Replace('_', '/');
            if (fixedUnderscoreString.Length % 4 != 0)
            {
                fixedString = String.Format("{0}", fixedUnderscoreString);
                int paddingCount = fixedString.Length % 4;
                while (paddingCount % 4 != 0)
                {
                    fixedString += '='; paddingCount++;
                }
            }
            else
            {
                fixedString = fixedUnderscoreString;
            }
            var inputBytes = Convert.FromBase64String(fixedString);
            return Encoding.UTF8.GetString(inputBytes);
        }

Let me know if you have the same problem and need me to further expand on the solution.

Good luck! 🙂

Advertisements

8 thoughts on “Authenticating mobile Facebook users

  1. Hey Peter i have been reading your posts for a while now and i have to say thank you =)

    But there is one more thing i would like you to do:
    I tryied this in my visual studio and could not quite get this working 😦

    would you mind appending a sample app please? i would really appreciate it =)

    best wishes,
    Joe

    • Hi Joe! Thank’s for your reply! I don’t have a sample app that I can attach, as it’s part of a much bigger solution. If you can tell me what part of the solution that’s not working for you, maybe I can detail the code examples?

      // Peter

  2. Hi,
    Small question… Where does the _facebookRequest come from in your code example??
    var response = _facebookRequest.GetTextResponse(new Uri(requestUrl));

    Thanks!
    Ed

    • Hi Ed,

      _facebookRequest is an abstraction layer that I have created for handling communication with Facebook. Its main responsibilities is to handle retry logic if communication with Facebook fails (which happens quite frequently) and to handle error messages from Facebook and mapping them to my own managed exceptions. In turn it calls another class that uses System.Net.WebRequest to send the REST-queries to Facebook.

      This part of the solution could also be handled with Facebook C# SDK.

      Let me know if there’s anything else in particular that you want to know.

      // Peter

  3. This is working fine in android but some problem in Iphone. The link to the inner page of the app is redirecting to apps.facebook .com/ app and showing the error mentioned in the article. What to do

      • Hi Peter
        My issue was closed, I tried the way you mentioned in this post. But that will take some time, I think it was the time to update the servers of FB.
        Any way nice post and thank you for your valuable time

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s