July 8, 2011

If you've ever had to integrate with Facebook Connect, which allows third-party web sites to rely on Facebook for user authentication, you may find yourself wanting to test whether your integration works. You can obviously use Selenium to automate the login process through the popup window, but what if you just wanted to try to skip this step and set the cookie yourself? Each time you login through the Facebook popup window, the JavaScript code will set an fbs_ cookie. The xxx in the fbs_xxxx cookie refers to the Facebook API key, and is signed with your application's secret key. If you have an infinite session key and a valid application access token, you can create your own fbs_xxx cookie. You can basically reverse the process that is normally done by the get_user_from_cookie from the Python Graph API library:

def get_user_from_cookie(cookies, app_id, app_secret):
    cookie = cookies.get("fbs_" + app_id, "")
    if not cookie: return None
    args = dict((k, v[-1]) for k, v in cgi.parse_qs(cookie.strip('"')).items())
    payload = "".join(k + "=" + args[k] for k in sorted(args.keys())
                      if k != "sig")
    sig = hashlib.md5(payload + app_secret).hexdigest()
    expires = int(args["expires"])
    if sig == args.get("sig") and (expires == 0 or time.time() < expires):
        return args
        return None

However, even when using a valid cookie signed by your app, it appears that Facebook invalidates the token. You end up seeing the same login popup window, but if you remove the all.js library and use a fake substitute, you'll notice that your app all of a suddenly works.  So Facebook is doing something internally to disallow self-signed fbs_cookies.

function do_nothing(a, b, c, d, e, f) {
    return true;

var FB = {
    'init': do_nothing,
    'getLoginStatus': function () {},
    'login': do_nothing

When you first run the init() function for the Facebook JavaScript SDK code, it first loads the cookie that you set (fbs_ + API_KEY) and sets FB_session to correspond to these cookie values. The problem is that another auth.request command is sent to Facebook. This auth.request command tries to verify whether a session key was obtained from Facebook. If it fails, then the FB._session object is cleared and that pop-up window appears asking you to login. The Facebook JavaScript does this auth.request by injecting an <iframe src="http://www.facebook.com/extern/login_status.php?"> with three different callback functions in the URL query string (no_user, no_session, and ok_session). The result from this IFrame request basically used to invoke the response, which is handled by either the no_user, no_session, or ok_session xdResponseWrappers. If the no_user/no_session response wrappers, then the 'connected' state will not be set internally and the session will be cleared. The code that invalidates this is located inside the cross-domain (aka 'xd') xdResponseWrapper function inside all.js:

xdResponseWrapper: function(a, c, b) {
   return function(d) {
     try {
       b = FB.JSON.parse(d.session);
     } catch (f) {}
     if (b) c = 'connected';
     var e = FB.Auth.setSession(b || null, c);
     e.perms = d && d.perms || null;
     a && a(e);

When the no_user response is called, d.session apparently is null, which will then be used by FB.Auth.setSession() to clear the FB._session object. You can't easily set a breakpoint on how this happens because the Facebook all.js code creates a function that does so, so it isn't apparent unless you insert alert() statements. But basically if you put alert statements, you will observe that the no_user result is returned when you attempt to just set the cookie and login to your site.

It looks like Facebook has implemented this policy to try to prevent people from using robots to directly login to their site....

It also appears that there are additional cookies that must be set (datr, lsd, c_user, h_user, lxe, and xs) in order for the login_status.php to return back the ok_session callback. You can observe this behavior by monitoring the cookie traffic if you were to login through the popup window. Apparently the 'xs' cookie is a well-known Facebook cookie too: http://www.duke.edu/~jyw2/wwwsecurity.html

blog comments powered by Disqus