Facebook SDK returned an error: Cross-site request

2019-01-08 13:50发布

问题:

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.

回答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.



回答2:

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

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

and it wil work or for more detail visit facebook login apps



回答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 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.



回答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



回答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 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 App\Http\Controllers;
session_start();
use Facebook\Facebook 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..



回答9:

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!



回答10:

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);


回答11:

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'];
      }
}
?>


回答12:

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 Facebook\PersistentData\PersistentDataInterface;
use Symfony\Component\HttpFoundation\Session\Session;

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()
        ]);


回答13:

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 \Facebook\Facebook([
    '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 (\Facebook\Exceptions\FacebookResponseException $e) {
    echo "Response Exception: " . $e->getMessage();
    exit();
} catch (\Facebook\Exceptions\FacebookSDKException $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.



回答14:

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.



回答15:

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



回答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 Facebook\PersistentData\PersistentDataInterface;
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 ( Facebook\Exceptions\FacebookResponseException $e ) {
    // When Graph returns an error
    echo 'Graph returned an error: ' . $e->getMessage();

}


回答23:

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.



回答24:

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!