Facebook SDK returned an error: Cross-site request forgery validation failed. The "state" param from the URL and session do not match

100,564

Solution 1

I found that as long as I enabled PHP sessions before generating the login url, and at the top of the script Facebook eventually redirects to, it works just fine on its own without setting a cookie (as per ale500's answer). This is using the 5.1 version of the sdk.

At the top of both scripts, I added...

if(!session_id()) {
    session_start();
}

...and it "just worked".

Here's a barebones complete example that worked for me:

auth.php

if (!session_id()) {
    session_start();
}

$oFB = new Facebook\Facebook([
    'app_id'     => FACEBOOK_APP_ID,
    'app_secret' => FACEBOOK_APP_SECRET
]);

$oHelper = self::$oFB->getRedirectLoginHelper();
$sURL = $oHelper->getLoginUrl(FACEBOOK_AUTH_CALLBACK, FACEBOOK_PERMISSIONS);

// Redirect or show link to user.

auth_callback.php

if (!session_id()) {
    session_start();
}

$oFB = new Facebook\Facebook([
    'app_id'     => FACEBOOK_APP_ID,
    'app_secret' => FACEBOOK_APP_SECRET
]);

$oHelper = self::$oFB->getRedirectLoginHelper();
$oAccessToken = $oHelper->getAccessToken();
if ($oAccessToken !== null) {
    $oResponse = self::$oFB->get('/me?fields=id,name,email', $oAccessToken);
    print_r($oResponse->getGraphUser());
}

Why?

As an additional note, this is explained in the Docs on the repo. Look at the warning on this page.

Warning: The FacebookRedirectLoginHelper makes use of sessions to store a CSRF value. You need to make sure you have sessions enabled before invoking the getLoginUrl() method. This is usually done automatically in most web frameworks, but if you're not using a web framework you can add session_start(); to the top of your login.php & login-callback.php scripts. You can overwrite the default session handling - see extensibility points below.

I'm adding this note because it's important to keep in mind should you happen to be running your own session management or if you're running multiple web servers in parallel. In those cases, relying upon php's default session methods won't always work.

Solution 2

insert this code after $helper = $fb->getRedirectLoginHelper();

  $_SESSION['FBRLH_state']=$_GET['state'];

Solution 3

Lots of great answers already mentioned, here is the one which helped for me,

I found that the problem is Cross-site request forgery validation failed. Required param “state” missing in FB code and here is the solution

After this line

$helper = $fb->getRedirectLoginHelper();

Add the below code,

if (isset($_GET['state'])) {
    $helper->getPersistentDataHandler()->set('state', $_GET['state']);
}

Solution 4

I got this error while using the Facebook SDK in Symfony2, writing a Twig Extension to display data from the API in templates.

The solution for me was adding 'persistent_data_handler'=>'session' to the Facebook object config, which causes the state data to be stored in a session key instead of memory:

$fb = new Facebook\Facebook([
    'app_id' => 'APP_ID',
    'app_secret' => 'APP_SECRET',
    'default_graph_version' => 'v2.4',
    'persistent_data_handler'=>'session'
]);

By default, it was using the built-in memory handler, which didn't work properly for me. Maybe because some functions are being called from within a Twig Extension, as the memory handler does work when using the SDK exclusively in normal controllers/services.

Apparently the state is set when you call getLoginUrl(), and is retrieved anytime you call getAccessToken(). If the saved state returns null (because your data handler isn't as persistent as it should be), the CSRF validation check fails.

If you need to treat sessions in a particular way or you want to store the state somewhere else, you can also write your own handler with 'persistent_data_handler' => new MyPersistentDataHandler(), using the FacebookSessionPersistentDataHandler as an example.

Solution 5

This happens when the Facebook library cannot match up the state param it receives back from Facebook with the one that it sets, by default, in the session. If you are using a framework such as Laravel, Yii2, or Kohana that implements its own session storage the standard Facebook session implementation will likely not work.

To fix it you need to create your own implementation of the PersistentDataInterface using your framework's session library and pass this to the Facebook\Facebook constructor.

Here's an example of a Laravel persistence handler from Facebook:

use Facebook\PersistentData\PersistentDataInterface;

class MyLaravelPersistentDataHandler implements PersistentDataInterface
{
  /**
   * @var string Prefix to use for session variables.
   */
  protected $sessionPrefix = 'FBRLH_';

  /**
   * @inheritdoc
   */
  public function get($key)
  {
    return \Session::get($this->sessionPrefix . $key);
  }

  /**
   * @inheritdoc
   */
  public function set($key, $value)
  {
    \Session::put($this->sessionPrefix . $key, $value);
  }
}

Example constructor params:

$fb = new Facebook\Facebook([
  // . . .
  'persistent_data_handler' => new MyLaravelPersistentDataHandler(),
  // . . .
 ]);

More info here: https://developers.facebook.com/docs/php/PersistentDataInterface/5.0.0

Share:
100,564
Fadi
Author by

Fadi

Updated on July 05, 2022

Comments

  • Fadi
    Fadi almost 2 years

    i'm trying to get Facebook user id using the php sdk like this

    $fb = new Facebook\Facebook([
        'app_id' => '11111111111',
        'app_secret' => '1111222211111112222',
        'default_graph_version' => 'v2.4',
    ]);
    
    $helper = $fb->getRedirectLoginHelper();
    
    
    $permissions = ['public_profile','email']; // Optional permissions
    $loginUrl = $helper->getLoginUrl('http://MyWebSite', $permissions);
    
    echo '<a href="' . $loginUrl . '">Log in with Facebook!</a>';
    
    
        try {
            $accessToken = $helper->getAccessToken();
            var_dump($accessToken);
        } catch (Facebook\Exceptions\FacebookResponseException $e) {
            // When Graph returns an error
            echo 'Graph returned an error: ' . $e->getMessage();
            exit;
        } catch (Facebook\Exceptions\FacebookSDKException $e) {
            // When validation fails or other local issues
            echo 'Facebook SDK returned an error: ' . $e->getMessage();
            exit;
        }
    
        if (!isset($accessToken)) {
            if ($helper->getError()) {
                header('HTTP/1.0 401 Unauthorized');
                echo "Error: " . $helper->getError() . "\n";
                echo "Error Code: " . $helper->getErrorCode() . "\n";
                echo "Error Reason: " . $helper->getErrorReason() . "\n";
                echo "Error Description: " . $helper->getErrorDescription() . "\n";
            } else {
                header('HTTP/1.0 400 Bad Request');
                echo 'Bad request';
            }
            exit;
        }
    
    // Logged in
        echo '<h3>Access Token</h3>';
        var_dump($accessToken->getValue());
    
    // The OAuth 2.0 client handler helps us manage access tokens
        $oAuth2Client = $fb->getOAuth2Client();
    
    // Get the access token metadata from /debug_token
        $tokenMetadata = $oAuth2Client->debugToken($accessToken);
        echo '<h3>Metadata</h3>';
        var_dump($tokenMetadata);
    
    // Validation (these will throw FacebookSDKException's when they fail)
        $tokenMetadata->validateAppId($config['11111111111']);
    // If you know the user ID this access token belongs to, you can validate it here
    //$tokenMetadata->validateUserId('123');
        $tokenMetadata->validateExpiration();
    
        if (!$accessToken->isLongLived()) {
            // Exchanges a short-lived access token for a long-lived one
            try {
                $accessToken = $oAuth2Client->getLongLivedAccessToken($accessToken);
            } catch (Facebook\Exceptions\FacebookSDKException $e) {
                echo "<p>Error getting long-lived access token: " . $helper->getMessage() . "</p>\n\n";
                exit;
            }
    
            echo '<h3>Long-lived</h3>';
            var_dump($accessToken->getValue());
        }
    
        $_SESSION['fb_access_token'] = (string)$accessToken;
    

    but it give me this error:

    Facebook SDK returned an error: 
    Cross-site request forgery validation failed. 
    The "state" param from the URL and session do not match.
    

    please any help i'm new in php and Facebook sdk's thank for any help in advance.

  • Daniel Steiner
    Daniel Steiner almost 8 years
    That's not how to fix this problem. I for example am using azure websites to test some stuff with the FB SDK and I am using the auto generated URL (xxxx.azurewebsites.net) and I get the error, even tough the source and target are the same.
  • Leo Galleguillos
    Leo Galleguillos almost 8 years
    I see how this works, but this just feels so tedious. Surely there must be an alternative rather than taking data from $_SESSION to $_COOKIE and then back to $_SESSION?
  • George
    George almost 8 years
    This is definitely not the way to go, pass your own implementation of the PersistentDataInterface in the config, see here: developers.facebook.com/docs/php/PersistentDataInterface/5.0‌​.0
  • naffiq
    naffiq almost 8 years
    Thanks, that helped me in Yii2 case too!
  • Luis Mejía F.
    Luis Mejía F. almost 8 years
    I coded my own library for CodeIgniter too. I use FILE caching.
  • WhiteHorse
    WhiteHorse about 7 years
    Such errors was intermittent in my case, which this piece of code fixed it.
  • Soojoo
    Soojoo almost 7 years
    where should I add the extra code, in the login.php or callback.php ?
  • G.Ashok Kumar
    G.Ashok Kumar almost 7 years
    You should add this in nextend-facebook-connect.php file which you will find in plugin folder, if your using wordpress
  • enobrev
    enobrev almost 7 years
    @JimmyPelton I just added a note to the bottom of my answer that links to the docs and explains why it works
  • 3DFace
    3DFace almost 7 years
    Be aware that such solution breaks CSRF protection. Because protection based on comparison of stored token and received one. Your code force them to match - you overwrite stored with received.
  • Emad Omar
    Emad Omar over 6 years
    It should be noted that this disables the CSRF protection offered by the SDK.
  • Phyllis Sutherland
    Phyllis Sutherland over 6 years
    @EmadOmar any idea why this is? Can you expand on that? Any valid way of fixing this while maintaining protection? EDIT: I see answer by G.Ashok Kumar explains it. Fx32's solution below seems to be best option.
  • Phyllis Sutherland
    Phyllis Sutherland over 6 years
    What about for when this happens only intermittently? Is there a fix for that? About 5% of the time this happens.
  • enobrev
    enobrev over 6 years
    @PhyllisSutherland I would probably put a check in before your call to the FB methods to make sure the session has been created properly. Maybe your session handler is failing somewhere that you're not seeing?
  • omalave
    omalave over 6 years
    I prefer this solution, instead of activate sessions using php functions, because I am using a framework
  • DanPride
    DanPride over 6 years
    >Be aware that such solution breaks CSRF protection. GREAT ! When facebook guys learn to do it properly I will reinstate it !! In the meantime I am damned disgusted with the waste of time, clearly not my doing.
  • sameerali
    sameerali over 6 years
    This helps me. I had to add session_start()
  • Leandro Bardelli
    Leandro Bardelli about 6 years
    You should NOT DO THIS
  • Leandro Bardelli
    Leandro Bardelli about 6 years
    @enobrev lol, the session of Schrödinger. not, really, thanks for your answer and your explanation. THIS IS THE WAY YOU MUST DO IT, DO NOT DISABLE CSRF PROTECTION offered by the SDK
  • Adriano G. V. Esposito
    Adriano G. V. Esposito about 6 years
    Why do check the existence of the session id? I read on the PHP docs: session_start — Start new or resume existing session So what's the purpose? Thanks.
  • enobrev
    enobrev about 6 years
    @AdrianoG.V.Esposito because that wasn't always the case. PHP used to throw an error (or at least a notice) when trying to start a session when one was already started. Also, why not? It doesn't hurt to check
  • Leandro Bardelli
    Leandro Bardelli about 6 years
    your debug is right but there is no solution, only a debug.
  • George
    George about 6 years
    > because I am using a framework < See my answer below for how to solve it when you are using a framework without breaking CSRF protection stackoverflow.com/a/38116933/1062129
  • fancoolo
    fancoolo over 5 years
    thanks. It solved to me but I've figured it out after half an hour because I needed to clean the browser cache also!! :)
  • Nick Tsai
    Nick Tsai over 5 years
    The reason is that FB would validate CSRF using session between login URL page and callback page.
  • PawelN
    PawelN over 5 years
    best answer so far :). btw/ here is the code for CI4 (pls put it in Libraries) <?php use Facebook\PersistentData\PersistentDataInterface; class FacebookPersistentDataInterface implements PersistentDataInterface { /** * @ var string Prefix to use for session variables. / protected $sessionPrefix = 'FBRLH_'; /* * @ inheritdoc / public function get($key) { return session()->get($this->sessionPrefix . $key); } /* * @ inheritdoc */ public function set($key, $value) { session()->set($this->sessionPrefix . $key, $value); } }
  • Admin
    Admin almost 5 years
    dude you are amazing! thanks for saving me a lot of effing time!
  • Crouching Kitten
    Crouching Kitten over 4 years
    To be more precise, the session has to be started before new Facebook.