Facebook PHP SDK 5 :: API 2.4 :: Cross-site reques

2019-01-22 21:37发布

问题:

I did a very simple PHP script, just to try to login via Facebook and get an accessToken. But when I try the following code, I get an Exception from the SDK : « Cross-site request forgery validation failed. Required param "state" missing. ».

Here is my code :

require_once __DIR__ . '/facebook-sdk-v5/autoload.php';
session_start();

$fb = new Facebook\Facebook([
    'app_id' => '{my-own-app-id}',
    'app_secret' => '{my-own-app-secret}'
]);

// Check to see if we already have an accessToken ?
if (isset($_SESSION['facebook_access_token'] )) {
    $accessToken = $_SESSION['facebook_access_token'];
    echo "Horray we have our accessToken:$accessToken<br />\n";

} else {
    // We don't have the accessToken
    // But are we in the process of getting it ? 
    if (isset($_REQUEST['code'])) {
        $helper = $fb->getRedirectLoginHelper();
        try {
            $accessToken = $helper->getAccessToken();
            } 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)) {
              // Logged in!
              $_SESSION['facebook_access_token'] = (string) $accessToken;

              // Now you can redirect to another page and use the
              // access token from $_SESSION['facebook_access_token']

              echo "Finally logged in! Token:$accessToken";
        }           
    } else {
        // Well looks like we are a fresh dude, login to Facebook!
        $helper = $fb->getRedirectLoginHelper();
        $permissions = ['email', 'user_likes']; // optional
        $loginUrl = $helper->getLoginUrl('http://mywebsite.com/myapp/index.php', $permissions);

        echo '<a href="' . $loginUrl . '">Log in with Facebook!</a>';
    }

}

exit;

回答1:

I had to add this lines in some servers:

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

I get this error randomly, depending of the server config.



回答2:

session_start() at the beginning of both the scripts. I got solution from here: https://github.com/facebook/facebook-php-sdk-v4/issues/473



回答3:

If you're using a "www" version of your site to generate the login link and you get redirected to the non-www version, you'll run into issues with your session. So make sure you access the www version of your site and then define the callback url to the same www version. Also, you can define permanent redirects in your server configuration to make sure anyone accessing your site from the non-www version gets redirected to the www version or vice versa.



回答4:

I also ran into the same problem and after researching on stackoverflow putting line

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

above has solved my problem

$helper = $fb->getRedirectLoginHelper();  


回答5:

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

$loginUrl = $helper->getLoginUrl('http://mywebsite.com/myapp/index.php', $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'].'/myapp/index.php', $permissions);


回答6:

Setting [PersistentDataHandler] explicitly before you get your tokens will guarantee that the request will be a success. Below shows how to fetch $_GET['state'] & simply inject it into the "to be used [helper]" on Symfony 2.x | 3.x

I had the exact same issue as the O.P. and this fixed my problem.

//create your new facebook sdk instance
$this->fb = new Facebook([
   'app_id' => $facebookAppId,
   'app_secret' =>$facebookAppSecret,
   'default_graph_version' =>$facebookDefaultGraphVersion
]);    

//retrieve the helper
$this->helper = $this->fb->getRedirectLoginHelper();

//below is the money shot
//setting this explicitly before you get your tokens will guarantee that the request will be a success. It Fetches $_GET['state'] & simply injects it into the "to be used [helper]"
$this->helper->getPersistentDataHandler()->set('state', $request->query->get('state'));


回答7:

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(); /*Add session start*/
include 'vendor/autoload.php';
include 'config.php'; /*Facebook Config*/
$helper = $fb->getRedirectLoginHelper();
$_SESSION['FBRLH_state']=$_GET['state']; /*Add This*/
try {
  $accessToken = $helper->getAccessToken();
} ?>


回答8:

For me the problem is solved now just by starting the session by adding this:

session_start();

at the beginning of both files (the first file generating facebook url and the callback file: login.php and fb-callback.php (https://developers.facebook.com/docs/php/howto/example_facebook_login)).

I also had to add this:

$config['app_id'] = 'myapp_id';

at the top of to prevent another non related error.



回答9:

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

you need to make sure your native PHP session feature is properly set.



回答10:

Laravel 5.2

I have this error too "Cross-site request forgery validation failed. Required param “state” missing".

and after reading this for hours. I tried to change the vendor script.

in vendor\facebook\php-sdk-v4\src\Facebook\Helpers\FacebookRedirectLoginHelper.php on line 123, I change this script:

private function makeUrl($redirectUrl, array $scope, array $params = [], $separator = '&')
{
    $state = $this->pseudoRandomStringGenerator->getPseudoRandomString(static::CSRF_LENGTH);
    $this->persistentDataHandler->set('state', $state);

    return $this->oAuth2Client->getAuthorizationUrl($redirectUrl, $state, $scope, $params, $separator);
}

into (I add Session::put('state', $state);)

private function makeUrl($redirectUrl, array $scope, array $params = [], $separator = '&')
{
    $state = $this->pseudoRandomStringGenerator->getPseudoRandomString(static::CSRF_LENGTH);
    $this->persistentDataHandler->set('state', $state);
    Session::put('state', $state);
    return $this->oAuth2Client->getAuthorizationUrl($redirectUrl, $state, $scope, $params, $separator);
}

and on line 234, I change this script:

protected function validateCsrf()
{
    $state = $this->getState();
    $savedState = $this->persistentDataHandler->get('state');

    if (!$state || !$savedState) {
        throw new FacebookSDKException('Cross-site request forgery validation failed. Required param "state" missing.');
    }

    $savedLen = strlen($savedState);
    $givenLen = strlen($state);

    if ($savedLen !== $givenLen) {
        throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
    }

    $result = 0;
    for ($i = 0; $i < $savedLen; $i++) {
        $result |= ord($state[$i]) ^ ord($savedState[$i]);
    }

    if ($result !== 0) {
        throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
    }
}

into (I added $this->persistentDataHandler->set('state', Session::get('state'));)

protected function validateCsrf()
{
    $state = $this->getState();
    $this->persistentDataHandler->set('state', Session::get('state'));
    $savedState = $this->persistentDataHandler->get('state');

    if (!$state || !$savedState) {
        throw new FacebookSDKException('Cross-site request forgery validation failed. Required param "state" missing.');
    }

    $savedLen = strlen($savedState);
    $givenLen = strlen($state);

    if ($savedLen !== $givenLen) {
        throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
    }

    $result = 0;
    for ($i = 0; $i < $savedLen; $i++) {
        $result |= ord($state[$i]) ^ ord($savedState[$i]);
    }

    if ($result !== 0) {
        throw new FacebookSDKException('Cross-site request forgery validation failed. The "state" param from the URL and session do not match.');
    }
}

that is all what I did. and the error gone.



回答11:

I ran into similar problem and found Nanang Koesharwanto's solution. It's more like quirk as modifying source files in vendor directory is a very bad idea. So here's the trick.

public function callback(Request $request)
{
    $this->helper->getPersistentDataHandler()->set('state', $request->state);
    return $this->helper->getAccessToken();
}

If it fails put use Session; in your controller.



回答12:

I was trying to implement Facebook login in Symfony with Facebook PHP SDK and I had the same error "Cross-site request forgery validation failed. Required param “state” missing".

I solved the problem by adding persistent_data_handler parameter to my facebook app instanciation with a custom handler which implements the PersistentDataInterface of Facebook PHP SDK.

Works like a charm.

public function facebookCallbackAction(Request $request) {
    $session = $request->getSession();

    $fb = new \Facebook\Facebook([
        'app_id' => $this->container->getParameter('facebook_app_id'),
        'app_secret' => $this->container->getParameter('facebook_app_secret'),
        'default_graph_version' => 'v2.5',
        'persistent_data_handler' => new SymfonyPersistentDataHandler($session),
    ]);
}

My custom handler :

use Facebook\PersistentData\PersistentDataInterface;
use Symfony\Component\HttpFoundation\Session\Session;

class SymfonyPersistentDataHandler implements PersistentDataInterface {
    protected $session;
    protected $sessionPrefix = 'FBRLH_';

    public function __construct(Session $session) {
        $this->session = $session;
    }

    public function get($key) {
        return $this->session->get($this->sessionPrefix . $key);
    }

    public function set($key, $value) {
        $this->session->set($this->sessionPrefix . $key, $value);
    }
}


回答13:

The real problem here was the encoding of my PHP file. I used UTF8, but it should have been UTF8 w/o BOM.

The side effect of the wrong encoding was that my SESSION was not working properly, and then the SDK wasn't able to retrieve the necessary informations to work properly.

A properly configured error reporting would have told that there was an issue straight way.

I think that we can fill this bug in the "noob" category. But still, I think it can be useful to other noobs like me.



回答14:

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.



回答15:

you have to make sure that the session start before the script runs. but again it will throw an 443: Network is unreachable if you start a session again on the same script. hope this helps some one.

I just used if (session_status() == PHP_SESSION_NONE){ session_start(); }



回答16:

To those of you of you who use cakephp 3.x and have this problem and you have no clue how to solve it. Add session_start(); at the beginning of your auth and callback method.

public function Facebookauth()
{
session_start();
 $fb = new Facebook([
      'app_id' => '{app_id}',
      'app_secret' => '{app_secret}',
      'default_graph_version' => 'v2.6',
     ..........

    ]);

you can still use 
     $session = $this->request->session();


回答17:

For me this was happening due to 'persistent_data_handler' . By adding this in Facebook config, I was able to make it work.

session_start();
$fb = new Facebook\Facebook([
  'app_id'     => '6XXXXXXXXX',
  'app_secret' => '5XXXXXXXXXXXXXX',
  'default_graph_version' => 'v2.6', 
  'persistent_data_handler' => 'session'
  ]);


回答18:

The fix for me was to change 'secure' => true to false in config/session.php. I had accidently set this to true while not using https in the first place.



回答19:

For me the solution was catching the exception by replacing the namespaced Facebook\Exceptions\FacebookSDKException to just Exception, because the script already used it.

use Facebook\Facebook;

// code ...

$fb = new Facebook([
    'app_id' => FB_APP_ID,
    'app_secret' => FB_APP_SECRET,
    'default_graph_version' => 'v2.5',
]);

$helper = $fb->getRedirectLoginHelper();
try {
    $accessToken = $helper->getAccessToken();
} catch (Exception $e) {
    echo $e->getMessage();
    exit;
}


回答20:

If the session_start() does not still solve the problem, you put a wrong URI in the Valid OAuth Redirect URI of your Facebook developer app.

To solve: First, go to your fb-callback.php, find the URI you put in the header function, example: header("Location: https://localhost/home.php"). Then go to your Facebook developer apps and then in the sidebar, click Facebook Login which is under the Products tab then click Settings. In the Valid OAuth Redirect URIs, add the URI you put in the header. From my example, I will put https://localhost/home.php.

Hope this helps.



回答21:

Finally after all this nice errors , i fixed all my problems with another solution , the example from facebook developer docs are outdated .

SDK V5 and APi 2.4 works like in this tutorial described , the access token need to be defined

Be sure you fill APP-IP|APP-SECRET with your credentials .

Example from tutorial :

$fb = new Facebook\Facebook([
      'app_id' => 'APP-ID',
      'app_secret' => 'APP-SECRET',
      'default_graph_version' => 'v2.4',
      'default_access_token' => 'APP-ID|APP-SECRET'
]);

Use this and not the example on developer facebook docs .

Have fun :)



回答22:

Whatever you do. Just don't call getAccessToken() more than once. It removes the state from session as soon as it is called.