<login with Facebook>
<create account>
<top items>
<latest items>
<about this site>
 
(27)
Making The World A Better Place With: Facebook Connect
or: "Technical Details About Mush Pot's Facebook Connect Integration"

For the benefit of other webmasters (used ironically) who might be working on the same, this is an account of Mush Pot's (MP) recent Facebook Connect (FC) integration, and particularly how MP accounts are linked with Facebook accounts. Mostly, it's stuff I wished I could have read in the developer docs.

FC is Facebook's single-sign-in system. (They describe it as something more, but that's how I prefer to think of it.) It lets you log into third-party websites (like MP) using your Facebook username and password, without disclosing that information to the third-party site. It's a little like Google Friend Connect or OpenID, but with some extra features, like letting users post blurbs about their activities back to Facebook. (If this paragraph was new information for you, you might want to start out with an FC tutorial.)


Example Facebook Connect Authentication

If a user has already logged into facebook.com (in another browser window, say), then they already have facebook.com cookies that authenticate them to Facebook as being that user. When a page from yoursite.com loads, you include a Facebook javascript file that bootstraps the FC javascript, and then, at the end of page load, you call the Facebook javascript function FB.init(). That function looks at the facebook.com cookies to see if the user a) is logged into Facebook and b) has "connected" to your site. If those two conditions are met, then a Facebook API session is established between the browser and Facebook. The javascript then stores that session information into special FC cookies for yoursite.com. The Facebook server signs all of those cookies, along with your application secret, and puts that in a cookie as well. The signature proves that Facebook has indeed authenticated the user, and your application server code should recalculate the signature and check that it is correct. If you are using a Facebook API client library, it may provide a facility for doing this for you.

Note: for security reasons, the browser doesn't really let FB.init() go and look at the facebook.com cookies to see if they're logged in, or to take the session information received from facebook.com and put it back into yoursite.com cookies. It has to go through some fascinating hoops to communicate across domain contexts like that.

Notice that a lot of the concepts--applications, "connecting" an application, API sessions--are the same as you'd encounter for a non-FC Facebook application. An FC application is just a Facebook application that has been configured with an FC callback URL; "connecting" is just another name for authorizing an application; and the API session is the same session you'd get when authorizing a non-FC application. FC proper is not much more than the glue that brings it together for you to run your application on your own site.

When FB.init() runs, it also refreshes the state of the yoursite.com FC cookies if necessary or clears the cookies if the user is no longer logged into Facebook or has de-authorized your application. On MP, the javascript isn't (currently) able to dynamically change the page DOM to reflect the fact that the user might now be logged in, so it passes {"reloadIfSessionStateChanged":true} to FB.init(). When the page reloads, the new FC cookies are sent to the yoursite.com server, which, in the case of MP, tries to associate the Facebook user with an MP user. On every page request (with a few exceptions), the server first looks at whether the request included cookies indicating an FC and/or MP session to determine the logged-in status.


Account Linking on Mush Pot

The simplest way to integrate accounts on your site with Facebook accounts is to not have normal accounts on your site at all, but to just automatically create a dummy account whenever a Facebook user you don't recognize requests the page and the FC cookies carry a valid session. However, on Mush pot, I wanted to:

 - continue allowing traditional (non-FC) accounts to be created and logged into with no Facebook interaction,
 - let Facebook users log in without having to provide MP any personal information like an email or password (in this case, a dummy Facebook-connected MP account is automatically created for the user), and
 - allow users to connect and disconnect their MP accounts with Facebook.

Aside: Facebook provides an API method called Connect.registerUsers that associates users on your site with Facebook users. However, for the purpose of actually linking your users with Facebook users, you don't need to call it. It's really only for telling your FC users which of their Facebook friends are also using your site but who haven't FC-connected their account. And for a site like MP that doesn't collect user email addresses, it's completely irrelevant.

In it's table of users, MP has columns for users' password hashes and for Facebook uids. Unfortunately, supporting all of the above requirements causes the number of possible states during a page load to balloon (e.g., user logged in with correct password but was already connected to a different Facebook user, etc.), so I tried to simplify this by enforcing that an MP account has a password associated if and only if it is not connected to a Facebook account: for every user in the database, either the password hash (and salt) or Facebook uid is null, but not both. I think this will simplify things from the user's perspective as well, since they don't have to keep track of two passwords.

But there are still lots of cases to handle. Basically, the combinations of these:

 - is there a valid logged-in MP session?
 - are the facebook cookies set?
 - does the cookie signature check out?
 - if there are valid MP and Facebook sessions, are the local and Facebook users linked in the database?
 - is the MP user linked to any Facebook user?
 - is the Facebook user linked to any MP user?

Here is the basic pseudocode for the MP logic that tries to condense and handle those states (if you see security issues or other problems, please reply to this post). Figuring this out took up most of the time I spent on this project:

// this condition actually just looks at whether the "user" cookie is set, which seems to
// indicate that they will all be set; it doesn't do the signature verification yet
if( the FC cookies are set )
  if( there is a valid logged-in MP session )
    if( the Facebook user and MP user are linked in the database )
      // just continue loading the page; we haven't checked the FC cookie signature,
      // but that should be fine: the user has proven that they are the MP user by
      // by having the session, and the link in the database proves that that user
      // was authenticated as the linked Facebook user at some point.
    else
      if( !the Facebook cookies signature checks out )
        // perhaps the cookies were tampered with; I don't worry too much about handling
        // this case nicely
        clear the FC cookies and redirect back to the front page
      else
        if( the MP user is linked to any Facebook user in the database )
          // this is a strange case that should be very rare; usually, the javascript will probably
          // prevent this; just switch the user to the Facebook user, since that puts us into a
          // known state and seems reasonable
          log the user out of MP
          log the user into MP as the user linked to the Facebook user
        else
          if( the Facebook user is linked to any MP user in the database )
            // perhaps the user apparently has two Facebook accounts? this redirects them to
            // a page that allows them to log out of Facebook only (and stay logged into MP)
            // or to log out of MP and log back in as the MP user linked to their Facebook
            // account; if they choose the first option, it forwards to a page that executes
            // this javascript:
            // FB.ensureInit ( function () { FB.Connect.logoutAndRedirect('/'); } );
            // The ensureInit() is important, since FB.init() runs asynchronously and method
            // calls that occur before it finishes won't run properly. Unfortunately,
            // logoutAndRedirect() shows the user a dialog box that says that they're logging
            // out of "this site and Facebook", which is not actually true, but I don't think
            // you can control that.
            redirect to a special page to handle this case
          else
            // automatically link the users (normally, this state will have been the result
            // of clicking the "connect accounts" link)
            link the MP user with the Facebook user in the database
            redirect the user to a user explaining the new account and asking them to select
                 a username
  else
    if( !the Facebook cookies signature checks out )
      clear the FC cookies and redirect back to the front page
    else
      if( the Facebook user is linked to any MP user in the database )
          // if the accounts are linked, then the user has already proven that the person
          // authenticated by Facebook is the same person with the MP account; this is
          // typically the result of the user clicking the "login with Facebook" link
          log the user into MP as the user linked to the Facebook user
      else
          // User probably just clicked "login with Facebook" and they don't already have an MP
          // account. Alternatively, I could have performed the automatic account creation in
          // response to a post to the Post-Authorize URL, but doing it here seems just as well,
          // and this way I don't have to remember in the database whether the user has had
          // a chance to change their username.
          create an MP account for the Facebook user
          call the Facebook API to get the user's real name, which is used as their initial userame
                      (modify the username to avoid collisions with existing)
          log the user into MP as the new user
          redirect to a page explaining what just happened and prompting them to change their
               username if desired
else
  // at this point, the user might be logged into MP or might not
  if( logged into MP and that user is linked to a Facebook user in the database )
    // this might be the result of stale cookies; when the user is connected to a Facebook user,
    // then they need to use Facebook authentication, so log them out and let them try again
    log the user out of MP


At the top of every page are links to control your logged-in status:
 - if the user is not logged in, there are links to create an MP account, log into an MP account, or log into a Facebook account
 - if the user is logged into MP only, there is a link to connect your MP account to Facebook, which just calls FB.Connect.requireSession() (wrapped in a call to FB.ensureInit())
 - if the user is logged into MP and Facebook, there are links to disconnect your accounts or to log out of both MP and Facebook.

Connecting accounts is relatively straightforward: calling requireSession() prompts the user to log into Facebook, and then the page reloads and the accounts are automatically connected by the page load logic.

Disconnecting accounts requires an additional screen because a disconnected account needs to password to be reassociated with it. The accounts are not disconnected until the user selects a valid password and clicks the "disconnect" button, which initiates a Facebook logout (which, unfortunately, incorrectly tells the user that they are being logged out of MP).


Note: I hope the above is helpful, but don't copy it verbatim for your site. You will need to make additional considerations for your situation, and there are considerations (e.g., why I have confidence that redirection to the special pages will always leave the connection in a supported state) that aren't discussed here.

I've posted a follow-up rant about my frustration with Facebook.
Travis    a year ago     tags:facebook connect facebook web development
<reply>    <no likey>   
(9)
By the way, the original subtitle for this post was "A young girl's strange, erotic journey from Milan to Minsk", which, in retrospect, doesn't seem quite appropriate. That's the power of revising right there.
Travis    a year ago    
<reply>    <no likey>