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

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

$fb = new FacebookFacebook([
    '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 (FacebookExceptionsFacebookResponseException $e) {
        // When Graph returns an error
        echo 'Graph returned an error: ' . $e->getMessage();
        exit;
    } catch (FacebookExceptionsFacebookSDKException $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 (FacebookExceptionsFacebookSDKException $e) {
            echo "<p>Error getting long-lived access token: " . $helper->getMessage() . "</p>nn";
            exit;
        }

        echo '<h3>Long-lived</h3>';
        var_dump($accessToken->getValue());
    }

    $_SESSION['fb_access_token'] = (string)$accessToken;

but it give me this error:

Read More
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.

Related posts

25 comments

  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 FacebookFacebook([
        '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 FacebookFacebook([
        '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.

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

      $_SESSION['FBRLH_state']=$_GET['state'];
    
  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']);
    }
    
  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 FacebookFacebook([
        '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.

  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 FacebookFacebook constructor.

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

    use FacebookPersistentDataPersistentDataInterface;
    
    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 FacebookFacebook([
      // . . .
      'persistent_data_handler' => new MyLaravelPersistentDataHandler(),
      // . . .
     ]);
    

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

  6. Finally, looking into FB code, I discovered that the problem

    Cross-site request forgery validation failed. Required param “state” missing

    and similars are caused by PHP variable $_SESSION['FBRLH_state'] that for some “strange” reason when FB call the login-callback file.

    To solve it I store this variable "FBRLH_state" AFTER the call of function $helper->getLoginUrl(...). Is very important to do only after the call of this function due to is inside this function when the variable $_SESSION['FBRLH_state'] is populated.

    Below an example of my code in the login.php:

    $uri=$helper->getLoginUrl($uri, $permissions);
    foreach ($_SESSION as $k=>$v) {                    
        if(strpos($k, "FBRLH_")!==FALSE) {
            if(!setcookie($k, $v)) {
                //what??
            } else {
                $_COOKIE[$k]=$v;
            }
        }
    }
    var_dump($_COOKIE);
    

    And in the login-callback.php before calling all FB code:

    foreach ($_COOKIE as $k=>$v) {
        if(strpos($k, "FBRLH_")!==FALSE) {
            $_SESSION[$k]=$v;
        }
    }
    

    Last, but not least, remember also to include code for PHP session so..

    if(!session_id()) {
        session_start();
    }
    ...
    ...
    ...
    ...
    <?php session_write_close() ?>
    

    I hope this response can help you to save 8-10 hours of work 🙂
    Bye, Alex.

  7. For me, the problem was that I wasn’t running a session before the script.

    So, I added session_start(); before instantiating the Facebook class.

  8. The Facebook object has an instance variable called persistentDataHandler, usually an instance of FacebookSessionPersistentDataHandler, which has a set and a get method to access the native PHP sessions.

    When generating the callback url using:

    $loginUrl = $helper->getLoginUrl($callback_url, $permissions);
    

    The methode FacebookRedirectLoginHelper->getLoginUrl() will create a 32-character random string, add it to the $loginUrl and save it to the code below using the persistentDataHandler‘s set method.

    $_SESSION['FBRLH_' . 'state']
    

    Later when the $helper->getAccessToken(); is called, the state param in the url will be compared to the stored in the same session to prevent CSRF. If not match, this will exception be thrown.

    FacebookSDKException: Cross-site request forgery validation failed. Required param “state” missing.

    All these being said, you need to make sure your native PHP session feature is properly set during this process. You can check it, by adding

    die($_SESSION['FBRLH_' . 'state']);
    

    after

    $loginUrl = $helper->getLoginUrl($callback_url, $permissions);
    

    and before

    $accessToken = $helper->getAccessToken();
    

    to see if that 32-character string is there.

    Hope it helps!

  9. With Symfony, it doesn’t work because the way session are managed.

    To resolve the problem you can create a new handler wich work with symfony’s session.

    FacebookDataHandlerSymfony.php :

    <?php
    
    use FacebookPersistentDataPersistentDataInterface;
    use SymfonyComponentHttpFoundationSessionSession;
    
    class FacebookDataHandlerSymfony implements PersistentDataInterface
    {
    
        private $session;
    
        public function __construct()
        {
            $this->session = new Session();
        }
    
        public function get($key)
        {
            return $this->session->get('FBRLH_' . $key);
        }
    
        public function set($key, $value)
        {
            $this->session->set('FBRLH_' . $key, $value);
        }
    
    }
    

    And when you create the FB Object, you have just to specifie the new class :

    $this->fb = new Facebook([
                'app_id' => '1234',
                'app_secret' => '1324',
                'default_graph_version' => 'v2.8',
                'persistent_data_handler' => new FacebookDataHandlerSymfony()
            ]);
    
  10. The same issue occurred to me on laravel 5.4 i solved this issue by putting

    session_start();
    

    at the top of the script.

    Below is the sample laravel controller namespace to give you a example how it will work.

    <?php
    namespace AppHttpControllers;
    session_start();
    use FacebookFacebook as Facebook;
    ?>
    

    the issue is occurring because is has not yet started so by adding session start at the top of the script we are just starting the session.

    hope it may help somebody..

  11. you receive this error if you origin hostname is different than the target hostname once authenticated.

    $loginUrl = $helper->getLoginUrl('http://MyWebSite', $permissions);
    

    with this statement, if the visitor on your website used http://www.mywebsite.com/ the cross-site error will be raised.

    You must ensure that origin and target hostname are exactly the same, including the eventual www prefix.

    Fixed version:

    $loginUrl = $helper->getLoginUrl('http://'.$_SERVER['SERVER_NAME'], $permissions);
    
  12. This might be kinda late but I hope it helps others since this problem still persists.

    I had this problem for a while and I’ve searched around and have seen a lot of different solutions, many of which disable the CSRF check. So after everything I’ve read, this is what worked for me.

    For what I understand, you get this error when your redirect URL doesn’t match the one you have setup on your app settings so my issue was fixed every easily but I have also seen people have issues by not having their session started properly, so I will cover both issues.

    Step 1: Ensure your session has started when it needs to.

    for example: fb-config.php

    session_start();
    include_once 'path/to/Facebook/autoload.php';
    
    $fb = new FacebookFacebook([
        'app_id' => 'your_app_id',
        'app_secret' => 'your_secret_app_id',
        'default_graph_version' => 'v2.10'
    ]);
    
    $helper = $fb->getRedirectLoginHelper();
    

    if your facebook callback code is on another file aside from the config, then start the session on that file too.

    for example: fb-callback.php

    session_start();
    include_once 'path/to/fb-config.php';
    
    try {
        $accessToken = $helper->getAccessToken();
    } catch (FacebookExceptionsFacebookResponseException $e) {
        echo "Response Exception: " . $e->getMessage();
        exit();
    } catch (FacebookExceptionsFacebookSDKException $e) {
        echo "SDK Exception: " . $e->getMessage();
        exit();
    }
    
    /** THE REST OF YOUR CALLBACK CODE **/
    

    Now, what solved my actual issue.

    Step 3: Set up your redirect URL in your app settings.

    In your Facebook Login app settings, go to the Valid OAuth redirect URIs where you should have added the url that points to your fb-callback.php file.

    http://example.com/fb-callback.php
    
    AND ALSO
    
    http://www.example.com/fb-callback.php
    

    then setup your redirect url as follows.

    $redirectURL = "http://".$_SERVER['SERVER_NAME']."/fb-callback.php";
    $permissions = ['email'];
    $fLoginURL = $helper->getLoginUrl($redirectURL, $permissions);
    

    Why both with and without www and why use SERVER_NAME?

    because your Valid OAuth redirect URI needs to match your redirect url in your code and if in you app settings you only set your OAuth redirect as http://example.com/fb-callback.php and set up your $redirectURL as http://example.com/fb-bacllback.php to make it match but the user entered your site as http://www.example.com then the user will get the Facebook SDK error: Cross-site request forgery validation failed. Required param “state” missing from persistent data because the URL the user is at, doesn’t EXACTLY match what you have setup. Why? I have no freaking idea.

    My approach makes it so if the user enters your site as http://example.com or http://www.example.com, it will always match what you setup in your app settings. why? because $_SERVER[‘SERVER_NAME’] will return the domain with or without the www depending on how the user entered the url in the browser.

    This are my findings and this is about the only thing that worked for me without removing the CSRF check and so far, no issues.

    I hope this helps.

  13. you could just do this set the session with the new state

    <?php
    if(isset($_GET['state'])) {
          if($_SESSION['FBRLH_' . 'state']) {
              $_SESSION['FBRLH_' . 'state'] = $_GET['state'];
          }
    }
    ?>
    
  14. In my case i have checked error and found error which lead me to solution with executing code:

    date_default_timezone_set('Europe/Istanbul');
    

    before script. Worked like a charm. For your location check: timezones.europe.php

  15. Easy fix for me.
    I changed:

    $loginUrl = $helper->getLoginUrl('http://www.MYWEBSITE.ca/index_callback.php', $permissions);
    

    to:

    $loginUrl = $helper->getLoginUrl('http://MYWEBSITE.ca/index_callback.php', $permissions);
    

    removing the 'www' solved the problem.

  16. The error is triggered if origin hostname is different than the target hostname [as Souch mentioned]. When visitors typed in URL address box as “http://website.com” that is different from “http://www.website.com“. We redirect them to the correct URL, by adding the following codes at topmost position before session_start().

      if($_SERVER['HTTP_HOST']!=='www.website.com'){
      header('location: http://www.website.com');
      }
    
  17. Yii2 solution that works for me:

    use FacebookPersistentDataPersistentDataInterface;
    use Yii;
    
    class PersistentDataHandler implements PersistentDataInterface
    {
        /**
         * @var string Prefix to use for session variables.
         */
        protected $sessionPrefix = 'FBRLH_';
    
        public function get($key)
        {
            return Yii::$app->session->get($this->sessionPrefix . $key);
        }
    
        public function set($key, $value)
        {
            Yii::$app->session->set($this->sessionPrefix . $key, $value);
        }
    }
    
  18. I had the same error, because I forgot to add “www.” to the sender address. In the Client-OAuth Settings there has to be the correct name.

  19. This is a common issue that many people facing in FB Api. this is only a SESSION problem. To solve this issue add some code like.

    On callback script usually fb-callback.php add “session_start();” just before you include the facebook autoload file. and then “$_SESSION[‘FBRLH_state’]=$_GET[‘state’];” after the “$helper = $fb->getRedirectLoginHelper();” line.

    Example :

    <?php 
    session_start();
    include 'vendor/autoload.php';
    include 'config.php'; /*Facebook Config*/
    $helper = $fb->getRedirectLoginHelper();
    $_SESSION['FBRLH_state']=$_GET['state'];
    try {
      $accessToken = $helper->getAccessToken();
    } ?>
    
  20. Might help someone, who is using Javascript Helper in frontend for authenticating the user and in PHP one is trying to to extract access_token from Redirect Login Helper. So use following

    getJavaScriptHelper();
    

    instead of

    getRedirectLoginHelper();
    
  21. SOLUTION FOR INTERMITTENT PROBLEMS

    I was a) redirecting to Facebook login link, b) redirecting from login.php to main.php. Users would travel to main.php and a few other pages, then click back back back in browser.

    Eventually, they would hit login.php with a bunch of creds posted to it, but Facebook removes the $_SESSION[‘FBRLH_state’] after a single success, so even though it had the proper $_GET[‘state’], it would error out.

    The solution is to a) track internally if the user is logged in and avoid the repeat Facebook logic in login.php, OR b) keep track of all recently valid state parameters for that particular user (in a session perhaps) which were set by Facebook and if the $_GET[‘state’] is in that array, then do this:

    $_SESSION[‘FBRLH_state’] = $_GET[‘state’];

    In this case you can do this safely without breaking CSRF protection.

  22. For me setting the session state worked

    Complete code ( in the redirect url php )

    $accessToken = '';
    
    $helper = $fb->getRedirectLoginHelper();
    
    if(isset($_GET['state'])){
    
        $_SESSION['FBRLH_state']=$_GET['state'];
    }
    
    
    try {
        $accessToken = $helper->getAccessToken();
    } catch ( FacebookExceptionsFacebookResponseException $e ) {
        // When Graph returns an error
        echo 'Graph returned an error: ' . $e->getMessage();
    
    }
    
  23. I am playing around with Symfony and was having this problem one attempt yes and the next one no.
    I solved this problem by storing the facebookRedirectLoginHelper object into session, and retrieving it later on from the session instead of asking the Facebook object for it again.

    The documentation (Symfony 4.3 at the time of this writing) states the following:

    Make sure your PHP session isn’t already started before using the Session class. If you have a legacy session system that starts your session, see Legacy Sessions.

    Symfony sessions are designed to replace several native PHP functions. Applications should avoid using session_start(), session_regenerate_id(), session_id(), session_name(), and session_destroy() and instead use the APIs in the following section.

    While it is recommended to explicitly start a session, a session will actually start on demand, that is, if any session request is made to read/write session data.

    So I think that retrieving the object from the session inherently starts the php session.

    If you are using some php framework, keep that in mind.

  24. For me the problem was different; (I was stupid)
    I had a popup window with Facebook login and again Facebook login buttons in Login/Signup page.

    • Whenever i click the Facebook button in the popup from other pages it worked fine.
    • But, when i click the Facebook button in the popup from Login/Signup page, it would not work.

    The reason was i had re-instantiated the Facebook and getRedirectLoginHelper objects. I had to comment out these statements in Login/Signup pages, as it was already available.

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

    is not the way to go. Although it worked.

  25. if you keep to see the error, just clean your browser cache.
    When I’ve fixed the problem using the enobrev solution, I was continuing to experience the error.
    After a while I’ve understood that I needed to clean the cache and after I’ve restarted the browser it worked!

Comments are closed.