I have an issue with some session functionality and the way Chrome does prefetching/rendering. I am attempting to interface a piece of forum software (esoTalk) with a custom laravel 4.3 app. I have authentication event listeners that cause laravel to create a php session (in addition the built in laravel session) that allows the forum and app to share authentication details. On access of the forum, and if the user is not logged in - but this shared information exists (i.e. the user has logged in on the laravel app), the forum will log in that user using the information available in the session.
For the most part this works fine except Chromes prefetching appears to be breaking things. If I monitor the forum using a debugger I can see that when I type out the forum url but before I hit enter chrome will access the forum. Following through with the debugger I can see it does everything it needs to do and is successful in logging in. As a final step the forum regenerates the session id to stop hijacking. This is where it breaks. It looks like chrome ignores the new session id (sent via a http SetCookie header) so that when I hit enter I go to the forum (and make a whole new request) using the original session id. This id doesn't exist so I get set up with a new one and consequently lose my logged in status. To the user this just looks like they never got logged in.
I've googled high and low for suggestions as to how I can work around this. I'm loath to removed the session id regeneration as it does serve a security purpose. I also can't disable the chrome prefetching/rendering. All in all I appear to be in a bit of a pickle.
I have created some code that replicates this. Though it relies on the prerendering kicking in (so you'll need to hit each of the files via the address bar a number of times)
// test1.php
<?php
function regenerateToken()
{
session_regenerate_id(true);
$_SESSION["token"] = substr(md5(uniqid(rand())), 0, 13);
$_SESSION["userAgent"] = md5($_SERVER["HTTP_USER_AGENT"]);
}
// Start a session.
session_set_cookie_params(0, '/');
session_name("SessionBork_Test_session");
session_start();
$_SESSION["SentryUserId"] = '99';
regenerateToken();
header('Content-Type: text/plain');
foreach ($_SESSION as $k => $v) {
echo $k . " = " . $v . "\n";
}
Access test1.php followed by test2.php and you should see a bunch of session variable output. As soon as prerendering/fetching kicks in you'll start getting a broken message.
// test2.php
<?php
function regenerateToken()
{
session_regenerate_id(true);
$_SESSION["token"] = substr(md5(uniqid(rand())), 0, 13);
$_SESSION["userAgent"] = md5($_SERVER["HTTP_USER_AGENT"]);
}
// Start a session.
session_set_cookie_params(0, '/');
session_name("SessionBork_Test_session");
session_start();
if (empty($_SESSION["token"])) regenerateToken();
// Complicate session highjacking - check the current user agent against the one that initiated the session.
if (md5($_SERVER["HTTP_USER_AGENT"]) != $_SESSION["userAgent"])
session_destroy();
// Log in a the user based on the SentryUserId
// ... logging in, setting userId, regenerating session
$_SESSION["userId"] = '10';
regenerateToken();
header('Content-Type: text/plain');
foreach ($_SESSION as $k => $v) {
echo $k . " = " . $v . "\n";
}
if ( ! isset($_SESSION['SentryUserId'])) echo "\n--\nPrerendering brokeded me.";
If you can get it hooked up to xdebug in an IDE or something you should see the hidden prerender hit to test2.php (which looks to be absolutely correct in the response) and then the subsequent actual hit when you press enter where it's forgotten who you are.