When a user comes to my login page, I generate a CSRF token:
if (empty($_SESSION['csrf_token']))
$_SESSION['csrf_token'] = bin2hex(openssl_random_pseudo_bytes(32));
and then display it as a hidden input in my form:
<input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
When the user clicks the login button, an AJAX request submits the user's details. In this process, it checks to see if the CSRF form value matches with the session variable:
if (!empty($_POST['csrf_token'])) {
if (hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
// Success! They matched
} else {
// CSRF session token did not match posted form token
die();
}
} else {
// CSRF token was missing from posted form
die();
}
In most cases, this works fine - the CSRF tokens match and they get logged in. But, in the event that a user goes to the login page and leaves it open for a couple of hours, and then comes back and attempts to submit the form without refreshing the page, then the PHP session will have expired and that causes the form to fail.
I'm assuming it fails because the $_SESSION['csrf_token'] variable is empty, because the session itself no longer exists. This is where the problem lies.
I have found a couple of solutions that could work but seem rather 'hacky'.
One solutions was to refresh the page when the user comes back to it after some inactivity using:
<META HTTP-EQUIV="REFRESH" CONTENT="csrf_timeout_in_seconds">
...and another solution was to use hashing from the sessionid and a server-side secret to create the CSRF token:
csrf = hash(sessionid+secret)
But, I'm thinking this would also suffer the same issue I'm currently having, because the session_id also exists in the session, meaning it also wouldn't exist when the session expires.
Would there be a problem using a PHP Cookie with a longer 'shelf-life' instead of the PHP session cookie which has a rather short life? Are there any alternative solutions that I'm missing?