Decoupling Drupal and Facebook logins

While Drupal for Facebook module seems to be the best and most flexible option out there for integrating Facebook with your Drupal website, one thing about it that ticks me off is its forced single sign-on approach between Facebook and Drupal.

Drupal for Facebook module (aka modules/fb) developed and maintained by Dave Cohen seems to be the best and most flexible option out there for integrating Facebook with your Drupal website, especially if you plan to do a bit more than just adding Like button or allowing your users to log in with Facebook.

And while I really like this module and all the features it has to offer, one thing about it that ticks me off is its forced single sign-on approach between Facebook and Drupal, which essentially means that:

  • logging in into Facebook automatically logs you in into your Drupal site,
  • logging out of Facebook automatically logs you out of your Drupal site,
  • logging out of your Drupal site automatically logs you out of Facebook.

There have been some heated discussions whether it should work this way or not, with pretty good arguments from both sides, and I don't really feel to be in position to judge which solution is the correct one.

I know what I needed though, and what I needed was decoupling those sign-ons. For all of you then who are in the same boat as I was and do not want this default behavior on your sites there seem to be a solution, which I originally described in my comment #15 of mentioned issue, and to which I have made some additional adjustment since then.

The solution presented below is based on modules/fb version 3.3 for Drupal 6. It has not been tested with other versions of Drupal or modules/fb, although I wouldn't be surprised if it worked there in the same or at least similar way.

Solution

On the Drupal for Facebook Settings page:

  • untick Use FB Cookie
  • tick Store tokens in session
  • tick Use Oauth when initializing javascript
  • untick Get login status when initializing javascript
  • tick Alternative verify login status when initializing javascript
  • tick Use tokens in session to initialize javascript
  • untick Append hash on javascript page reload

This should take care of logging in and logging out in Facebook.

Now to "fix" logging out in Drupal, we need a bit of custom code for handling user logout operation, which is going to replace modules/fb logic.

In one of your custom modules (for the sake of example let's assume it is called MYMODULE) add the following code:

/**
 * Implementation of hook_user().
 */
function MYMODULE_user($op, &$edit, &$account, $category = NULL) {
  if ($op == 'logout') {
    if (fb_facebook_user() && fb_api_check_session($GLOBALS['_fb'])) {

      // Exterminate Facebook cookies!
      // I needed to add cookie domain to all setcookie() calls to make them
      // work - this could depend on specific (sub)domain configuration I guess.
      $cookie_domain = '.mydomain.org';
      // When "Use Oauth when initializing javascript" setting is enabled.
      if (variable_get(FB_VAR_JS_OAUTH, TRUE)) {
        if (isset($_COOKIE['fbsr_' . $GLOBALS['_fb_app']->apikey])) {
          setcookie('fbsr_' . $GLOBALS['_fb_app']->apikey, '', time() - 3600, '/', $cookie_domain);
        }
        if (isset($_COOKIE['fbsr_' . $GLOBALS['_fb_app']->id])) {
          setcookie('fbsr_' . $GLOBALS['_fb_app']->id, '', time() - 3600, '/', $cookie_domain);
        }
      }

      // When "Use Oauth when initializing javascript" setting is disabled.
      else {
        if (isset($_COOKIE['fbs_' . $GLOBALS['_fb_app']->apikey])) {
          setcookie('fbs_' . $GLOBALS['_fb_app']->apikey, '', time() - 3600, '/', $cookie_domain);
        }
        if (isset($_COOKIE['fbs_' . $GLOBALS['_fb_app']->id])) {
          setcookie('fbs_' . $GLOBALS['_fb_app']->id, '', time() - 3600, '/', $cookie_domain);
        }
      }

      // Exterminate Facebook variables too!
      unset($_SESSION['fb_' . $GLOBALS['_fb_app']->id . '_user_id']);
      unset($GLOBALS['_fb_app']);
      unset($GLOBALS['_fb']);
      unset($GLOBALS['fb_init_no_settings']);

      // The part below is remaining code from user_logout(), which would
      // normally be executed after calling user_module_invoke('logout'), but
      // because we are doing drupal_goto() here (to avoid call to drupal_goto()
      // in fb_user module, which would log user out of Facebook) we need
      // to execute this code here too.

      // Destroy the current session, and reset $user to the anonymous user.
      session_destroy();

      // Load the anonymous user.
      $user = drupal_anonymous_user();

      drupal_goto();
    }
  }
}

Now, this is not a very decent behavior here with that drupal_goto() at the end of our hook implementation, but unfortunately it has to be there as explained in code comments, otherwise modules/fb's implementation of hook_user() would be called after this and still log your user out of Facebook, which is exactly what we are trying to avoid here.

(Nota bene, modules/fb does exactly the same thing in its fb_user_user() function, sending user to Facebook's logout page using drupal_goto() and potentially skipping all other hook_user() implementations provided by other modules, but there already is a relevant issue for this.)

Finally, some modules' weights need to be updated, so that MYMODULE's weight is higher than any other module weight, but lower than fb_user weight - this is to make sure that hook_user() implementation for $op == "logout" from fb_user module will never get called (and thus users will never be redirected to Facebook's logout page), but all other hook_user() implementations will still be called.

In my case, the "heaviest" module before was autologout with weight = 1000, so I have set MYMODULE's weight to 1010 and fb_user's weight to 1011.

Note here that fb_user sets its own weight to -1 during installation process for form altering purposes. It was not important though in my case and I could easily change it without any side effects.