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

Parallelizing Facebook FQL queries to increase performance

This post really doesn’t have anything to do with Azure, but as my blog has a taste of Facebook, I’ll throw it out there anyhow.

Am I Interesting is an application that reads quite a bit of data from Facebook and needs to execute five different queries against Facebook for each user that is rated to have enough data to calculate the final rating. In the original solution, I performed these queries in a synchronous sequence like this:

I now attempted to instead run the queries as much as possible in parallel:

The code I for testing the parallelism, using the .NET 4.0 Task Parallel Library:

I created a simple web page that displays the results and compares the sequential to the parallel processing:

As you can see, this modification cut the total query time in about half!

For now, the performance after the change to parallel query execution is good enough. The next thing to try when/if we require even faster performance is sending off all queries to Facebook in one call using fql.multiquery.

Testing/QA environment in Azure for Facebook application

With ongoing quite extensive modifications of core business rules for Am I Interesting, I need a testing environment where I can deploy my application so that other selected people (my partners in crime) can help test out that everything works the way it should before we release into production.

My initial idea was to do this by deploying to the staging slot of my existing Hosting Service is Azure, but this was not a good solution for me because:

  • I need a predictable URL for this environment.¬†When deploying to the staging slot in Azure, a GUID-looking URL is automatically generated that cannot be changed, and this does not work for me since I have to put this URL in the Facebook application settings.
  • I am using¬†Facebook C# SDK¬†for authentication from my ASP.NET¬†MVC application that reads all Facebook settings, including the application URL,¬†from web.config, which cannot be modified after deployment. And I don’t know the URL before¬†the application is deployed.
  • The staging slot for Azure is really intended for a final smoke test before going into production and should therefore have a production-like physical setup, but for my testing environment I only want/need one Extra Small instance to save cost.

The solution therefore was to create a new Hosted Service and use its production slot, which has a known URL, for my testing environment:

An arguable disadvantage of using a new Hosting Service with a predictable and fixed public URL is that it’s more likely to be hit by users who shouldn’t have access to the testing environment. However, this is not a big issue in my case since the Facebook sandbox mode prevents users, other than those that I have explicitly configured, from getting access to the application.

As any other testing environment, I need storage that’s separated from production, so I have created a new SQL Azure database also setup a new storage account¬†for tables and queues¬†(the “p2piter” account below is the one I use in production):

I have then setup a new Facebook application (in sandbox mode) and pointed that application to the new URL:

To ease deployment, I have added a new Service Configuration with settings specific for the testing environment, like connection strings to Azure Storage and SQL Azure:

Also, I’ve added a new build configuration with a corresponding¬†web.config transformation that contains, among other things, the Facebook application settings that the Facebook C# SDK reads:

When it’s time to deploy, I can now simply choose my new service and build configuration to create the complete deployment package:

Done! ūüôā

Using ACS for Facebook Authentication from WP7 (Part 1/2)

On top of the ASP.NET MVC web app for Am I Interesting (AII), I’m also adding a Windows Phone 7 client application. This application needs to first authenticate against Facebook and retrieve a Facebook access token before passing¬†that token when authenticating against the API for AII.

This is a two-part blog post:
Part 1 – Configuring ACS for Facebook Authentication
Part 2 – Implementing ACS in your WP7 app (next post)

For this authentication, I will use Azure AppFabric Access Control Service (ACS) from the WP7 app. ACS¬†offers a convenient way to¬†authenticate against identity providers such as Windows Live, Facebook, Google and Yahoo, and it’s priced at only $1.99 per 100,000 transactions. If you, like me, have an MSDN subscription, you also have 100,000-500,000 transactions per month included free of charge.

To set up ACS, go to the Windows Azure Management Portal, navigate to “Service Bus, Access Control & Caching” and click to create a new service namespace:

Check the Access Control check box and enter a unique namespace, “amiinteresting” in my case.

The next step is to add Facebook as an identity provider to your created service namespace. Click to manage Access Control Service:

Click on Identity providers and then Add:

Enter a display name, your Facebook Application Id and¬†Facebook Application Secret¬†(from the Facebook developer portal), and a comma-separated list of Application permissions that you need. Offline_access is needed if you don’t want your WP7 users to have to enter their login credentials every time they start the app:

I have added two Identity providers for the two different Facebook applications I use for Development (local debugging) and Production with different Application Ids and Application Secrets.

The next step is to tell ACS about the client application (relying party) that will connect to ACS to authenticate users. To do this, navigate to Relying party applications and click on Add:

For a thick client application, that will not use browser redirection, it’s enough to enter a display Name, a URI¬†Realm that the token should be issued for (a bit tricky subject, described here), which in my case is the application URI https://www.amiinteresting.net, and a Token format. I have chosen a Simple Web Token (SWT) which is suitable for tokens that are be passed in the HTTP header or request, which is normally the case when communicating with REST (or REST-alike) API:s:

Further down on the page, choose the Identity provider that was added in a previous step and click to Generate a token signing key for this relying party application:

The last thing you need to do is configure the Rule group that was automatically created when you added the relying party. A rule group is used to define how claims from the identity provider (Facebook) are passed to the relying party (WP7 app). Navigate to Rule groups and click on the automatically added rule group to configure:

On the screen that appears, no rules will have been added yet. The rules can be added individually/manually, or you can click on Generate to automatically generate rules for you:

On the next screen, I have chosen to generate rules for both my Facebook applications (development and production):

I then modified the generated rules by removing the emailaddress claim (I don’t need and therefore didn’t configure the Identity provider to request the email permission from Facebook) and changing the description of the nameidentifier claim to “Facebook User Id” (which it is):

Done! ACS is now configured for Facebook authentication and ready to be used by WP7!

Stay tuned for the next post that covers how to connect the WP7 app to ACS and ultimately get the Facebook access token, which is required to authenticate against the API for Am I Interesting….