Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Facebook PHP SDK session lost, needs JS SDK refresh

Really strange, it seems like the Facebook session is lost ($user = 0) using the PHP SDK (v 3.2.2). The JS SDK needs to reload my page to recover the Facebook session. This problem occurs now and then, sometimes the Facebook session is lost, sometimes it works just fine.

session_start();

// Run Facebook API
$facebook = new Facebook(array(
  'appId'  => $config['facebook']['id'],
  'secret' => $config['facebook']['secret']
));

// Fetch user
$user = $facebook->getUser();
if($user) {
  try {
    // Just to be sure, add access token to each request
    $user['access_token'] = $facebook->getAccessToken();    

    // Fetch user details
    $user = $facebook->api('/me?access_token='.$user['access_token']);          
  }
  catch (FacebookApiException $e) {
    error_log($e);
    $user = null;
  } 
}

The main problem is that $user = $facebook->getUser(); returns 0. However, as the user is actually connected, the JS SDK detects the auth.login event and reloads the page (another strange thing, I use auth.login because the auth.authResponseChange event keeps getting fired every second). Now $user = $facebook->getUser(); returns the user's uid.

window.fbAsyncInit = function() {
  FB.init({
    appId : appId,
    channelUrl : '//domain.com/signon/channel.php',
    status     : true,
    cookie     : true,
    xfbml      : true 
  });
  FB.Event.subscribe('auth.login', function(response) {
    location.reload();
  });
};

In Facebook Developer I defined the App Domain (domain.com) and Site Url (http://domain.com/). Already tried Site Url with/without trailing slash, didn't solve the problem.

Any idea what's going on, why the Facebook PHP SDK doesn't detect the user's session immediately / keeps losing the user's session and needs a reload? This problem really causes a bad UX.

Thanks a lot!

Robin

like image 854
Robin Avatar asked Jan 05 '13 12:01

Robin


1 Answers

I've come accross the same problem lately, it seems that if after 5mins or so of inactivity you do a reload, the session does not persist. Sometimes it works, sometimes it doesn't.

I've been looking into it for about the last week, and the only solution I could think of was to use the JS SDK to do a page reload with:

FB.Event.subscribe('auth.login', function(response) {
      window.location.reload();
    });

But I agree, it's not a very elegant solution in terms of UX. You should pass the cookie param in the PHP SDK, and the oauth param in JS SDK, and see if that works (it didn't for me):

$facebook = new Facebook(array(
  'appId'  => $config['facebook']['id'],
  'secret' => $config['facebook']['secret'],
  'cookie' => true
));

AND

FB.init({
    appId : appId,
    channelUrl : '//domain.com/signon/channel.php',
    status     : true,
    cookie     : true,
    xfbml      : true,
    oauth      : true
  }); 

Stranger even still, I re-downloaded the latest PHP SDK from Github and uploaded it to a sandbox environment (Apache, PHP 5.4). I ran the example (with the JS SDK) AS-IS and it has the same issue!

Now if the above just doesn't cut the mustard, I have some more suggestions.

CHANGING THE SDK UP A BIT

Firstly, passing

$facebook->getAccessToken();

will do you no good if $user returns 0. However, after doing a bit of digging around in base_facebook.php, I noticed that the method getCode() actually uses $_REQUEST to retrieve the authorization code from the query parameters.

from the PHP Manual, $_REQUEST is

An associative array that by default contains the contents of $_GET, $_POST and $_COOKIE.

BUT

It is very different to $_GET,$_POST or $_COOKIE.

This can be a bit of a bugger depending on your setup. So instead, find the function getCode() in base_facebook.php that looks like this:

protected function getCode() {
if (isset($_REQUEST['code'])) {
    if ($this->state !== null &&
            isset($_REQUEST['state']) &&
            $this->state === $_REQUEST['state']) {

        // CSRF state has done its job, so clear it
        $this->state = null;
        $this->clearPersistentData('state');
        return $_REQUEST['code'];
    } else {
        self::errorLog('CSRF state token does not match one provided.');
        return false;
    }
}

return false;
}

and merge the query into a new array/variable (let's call it $code_array) to include the $_GET, $_POST & $_COOKIE arrays using array_merge() like so:

$code_array = array_merge($_GET,$_POST,$_COOKIE);

What you end up with is an array that contains all the data from the respective requests. The just go ahead and replace $_REQUEST with $code_array inside the function, i.e

\\replace $_REQUEST with $code_array;
if (isset($code_array['code'])) {
if ($this->state !== null &&
        isset($code_array['state']) && 
        $this->state === $code_array['state']) { //...and so on for the rest

This should do the job nicely (hopefully).

OPTIONAL - Extending your Access Token Lifetime

This is optional, but I included it anyway. Most New Apps will already have long lived Access Tokens, but just in case, you can use the $facebook->setExtendedAccessToken(); method to transform your existing access token.

Note: You have to call $facebook->setExtendedAccessToken(); before you actually get your access token with the getAccessToken() method.

Now, using your code you will have

$user = $facebook->getUser();
  if($user) {
    try {
    // Just to be sure, add access token to each request
    $facebook->setExtendedAccessToken();
    $access_token = $facebook->getAccessToken();
    // Fetch user details
    $user = $facebook->api('/me?access_token='.$access_token);
    }
//and so on..

CONCLUSION

Breaking $_REQUEST into $_GET,$_POST & $_COOKIE (Even though it includes all three by default) seems to ensure we can fetch the cookie that was set by the SDK(s) without much trouble. I say seems because heck, I'm not really 100% sure myself.

I Used the above methods to get it working in my case, so I'm hoping to share some knowledge, as I've been losing far too much sleep over this problem, and couldn't find a viable solution.

Hope this helps!

EDIT: I forgot to mention, changing the API request from

$user_profile = $facebook->api('/me');

to

$user_profile = $facebook->api('/'.$user.'?access_token='.$access_token);

was something I also did, and it made a difference. :)

like image 159
Drian Naude Avatar answered Sep 22 '22 11:09

Drian Naude