Updating Cookies for Facebook Connect

One of the amazing things about Facebook Connect is that it allows developers to incorporate social-content from Facebook into their own sites, break the user free from the confines of the facebook.com site, and offer a more 'seamless' experience from the end-user's perspective.

For those that don't know, the magic of Facebook Connect is largely driven using cross-domain communication within a browser session to pass information back and forth between Facebook and the end-user. State information like the user's login credentials and session information are stored within the user's cookies and a large library of javascript code mediates the transfer of information between facebook, the user's browser session, and the user's cookie stores.

One of the primary challenges facing engineers who use web develpment frameworks like Ruby on Rails is that the heavy reliance on browser-based javascript and cookies is that it breaks the Model-View-Controller (MVC) architecture, leading to all sorts of development headaches. Since the session keys that the server requires to make queries to Facebook's API are stored in cookies and not passed to the controller in a HTTP request, this can lead to strange situations where the session state stored on the server-side does not match the session state stored in the cookies.

The standard solution seems to be simply to force the user to reload the page, which will refresh the cookies, which the server-side controller code can then grab on the next reload. However, this often leads to a poor end-user experiences where the user is forced to reload pages several times in order to syncrhonize the information stored in cookies with the information stored in the server. Even worse, the web site may simply cease to work in certain cases (e.g., if the user logs out of facebook while logged into the current site). For an interesting discussion on this, I found this nice post.

In this post, I'll be discussing an easy way to get around this problem for a simple case that popped up during development of a feature here at Posterous: prompting the user for the "offline_access" permission. I'll run through the situation, the naive solution using page reloads, and the "more elegant" solution (no doubt there are more elegant solutions, but I found this works fine).

The situation

This seems to be a very common scenario. Say your application would like to push content to a user's profile without the user being actively engaged in your site. To do so, the user needs grant your site the "offline_access" permission. This changes the session key from a temporary key (valid for a short amount of time) to a persistent key, which your site will need to capture and store in a database. I'm assuming that you're using Ruby on Rails using the Facebooker gem (though the solution will be more or less general for any MVC architecture).

Naive Solution

The problem with the simple scenario outlined above is that the session keys are stored as cookies which are not passed to your site until the next reload. The native solution would be to have to user reload several times:

1) User visits the site and is presented with a "grant offline access" link. Facebook sends a temporary session_key, which is stored in the user's cookies.

2) User clicks on link and grants the offline_access permission via the pop-up window. Cookies ARE NOT updated at this point. Page is refreshed by some javascript.

3) Server-side controller code grabs a copy of the session_key stored in cookies, which is still temporary, since the javascript code that lives in the HTML hasn't had a chance to update the cookies.

4) Server renders the view, which includes the javascript that refreshes the cookies and stores a permanent session_key. User clicks another link to reload the page.

5) Server-side controller code grabs a copy of the correct, persistent session_key and stores it in the database.


My Solution

1) User visits page, clicks on a 'grant access link'. User automatically redirected another page (call it refresh_cookies) which refreshes cookies automatically, which then sends the user automatically to the original page.

2) Controller code grabs the updated session_key on the next page.

The key to this code is using a bit of Facebook's javascript to ensure that the user is redirected only after cookies have been refreshed. Without this, we can't be sure that the cookies have been updated on the next view. The code for the rails view that accomplishes this is:

<script src="http://static.ak.connect.facebook.com/js/api_lib/v0.4/FeatureLoader.js.php" type="text/javascript"></script>

<script type="text/javascript">

window.onload = function() { 
    FB_RequireFeatures(["XFBML"], function() { 
        FB.Facebook.init('YOURAPIKEY','/xd_receiver.html'); 

        FB.XFBML.Host.get_areElementsReady().waitUntilReady(function() { 
            //alert('All XFBML elements loaded'); 
            window.location.href='DESTINATION';
        }); 
    }); 

}; 
</script>

<h4>Updating Facebook settings for <%= fb_name(@fb_user, {:useyou => "false"}) %> <%=image_tag('loading.gif')%></h4>

By sticking a bit of FBML (rendered using the "fb_name" helper method in Facebooker) into the view and waiting for it to render, you've ensured that the cookies stored in the browser are the most recent ones, which will be captured and saved by the controller after the redirect. The 'loading.gif' is simply a dynamic image that will hopefully prevent your user from thinking nothing is happening. In practice, the simple XFBML on this page takes about 1-2 seconds to load -- a long time, but necessary to ensure that we have correct session_keys.

Thanks for reading -- see you guys on facebooker-talk!

Posted