Luna Luna - 1 year ago 98
PHP Question

The “state” param from the URL and session do not match when leaving browser open too long

Im getting this error when leaving my browser window open for an extended amount of time say 3 hours , then trying to log in to my site through Facebook using Facebook sdk:

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

Im using laravel 5.3 , if i open a new window then try to log in everything works fine.

I know it has something to do with sessions.

I want it so that if that error appears all a user has to do is refresh the page and then they can login using Facebook.

the problem is if a user logs in using Facebook , they get redirected to mysite/facebookcallback , then on refresh its again my site/facebookcallback and the same error appears .

i'm assuming some how i need to create a new session , in the case a user leaves there browser window open for say 3 hours then tries to log in using Facebook
a new session gets created thus avoiding the error, but i only need to do that if the session is old.

heres a portion of the code I'm using

try {
$token = $fb->getAccessTokenFromRedirect($redirectURL);
} catch (Facebook\Exceptions\FacebookSDKException $e) {
dd($e->getMessage());
}

// Access token will be null if the user denied the request
// or if someone just hit this URL outside of the OAuth flow.
if (! $token) {
// Get the redirect helper
$helper = $fb->getRedirectLoginHelper();

if (! $helper->getError()) {
abort(403, 'Unauthorized action.');
}


the getRedirectLoginHelper() is as follows

protected function validateCsrf()
{
$state = $this->getState();
if (!$state) {
throw new FacebookSDKException('Cross-site request forgery validation failed. Required GET param "state" missing.');
}
$savedState = $this->persistentDataHandler->get('state');
if (!$savedState) {
throw new FacebookSDKException('Cross-site request forgery validation failed. Required param "state" missing from persistent data.');
}

if (\hash_equals($savedState, $state)) {
return;
}

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


you can see the error message Cross-site request forgery validation failed. The "state" param from the URL and session do not match

I don't want to touch the getRedirectLoginHelper code because thats from Facebook's sdk

I need to come up with a fix before it get that far

Answer Source

Kai is right. It has to do with the session expiring. To expand a bit further.

The Problem

Say you generate a login URL and display it on your site somewhere:

$fb = new \Facebook\Facebook([
  'app_id' => '{app-id}',
  'app_secret' => '{app-secret}',
  'default_graph_version' => 'v2.10',
  ]);

$helper = $fb->getRedirectLoginHelper();

$loginUrl = $helper->getLoginUrl('https://example.com/fb-callback.php');

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

The URL that gets generated contains a state param. Behind the scenes getRedirectLoginHelper() will store the value of state in a session.

Say your session expires in 20 minutes and someone loads the page with a Facebook login URL. They wait 21 minutes and then click on the login button. By that time the session will be blank so the state value will be missing. The user will be asked to authorize the app, then when they are redirected back to your callback URL with the original state param appended to it, the getAccessTokenFromRedirect() method will try to compare the state param in the callback URL with the session. Since the session expired, it will cause that error.

The Solution

When I add a "Log in with Facebook" button to my site, I have it point to an endpoint that I own. Say /facebook/login for example. On that endpoint, I invoke the getLoginUrl() method and automatically redirect the user.

So somewhere on my page I have:

<a href="/facebook/login">Log in with Facebook</a>

Then on the /facebook/login endpoint I have:

$fb = new \Facebook\Facebook([
  'app_id' => '{app-id}',
  'app_secret' => '{app-secret}',
  'default_graph_version' => 'v2.10',
  ]);

$helper = $fb->getRedirectLoginHelper();

$loginUrl = $helper->getLoginUrl('https://example.com/fb-callback.php');
header('Location: ' . $loginUrl);
exit;

That way you're saving the state the moment they click on the link instead of the moment the page loads. Now if they take 21 minutes to authorize the app, you'll still get the error above, but that's way less likely than someone just leaving your page open for a long time. :)

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download