I’m having some trouble with implementing CSRF protection on a login form. Here is the general flow of the login:
This is included at the top of the login page:
// Create CSRF token
$token = $auth->random(64); // 64 psuedorandom characters from /dev/urandom
$_SESSION['token'] = $token;
The login form:
<form action="<?php echo $_SERVER['PHP_SELF']; ?>" method="post">
<input type="text" name="username" />
<input type="password" name="password" />
<input type="hidden" name="token" value="<?php echo $token; ?>" />
<input type="submit" name="login" value="Login" />
</form>
And finally, when the form is submitted further down the page:
if (isset($_POST['login'])) {
// Bind input to variables
$username = isset($_POST['username']) ? $_POST['username'] : '';
$password = isset($_POST['password']) ? $_POST['password'] : '';
$posttoken = isset($_POST['token']) ? $_POST['token'] : '';
// Attempt to login
$auth->login($username, $password, $posttoken);
}
The problem starts when $auth->login receives the input. The $_SESSION token is equal to the generated token, but the $_POST token is equal to what the $_SESSION token was during the last submit.
Example var_dumps from $auth->login:
First submit var_dump:
$_SESSION Array
[token] => 00a28586a1a89b30149ef130ca6f3c01a25435ad1b0ad1a19326205c75b80d79
$_POST Array
[username] =>
[password] =>
[token] => 2200bb8663f19d66639a7f4791ddb53c9d510802d0ed76c42ac8b3f6d9e1589a
[login] => Login
Second submit var_dump:
$_SESSION Array
[token] => e093e312b379d766d46083d616fa8655f1565dc19ed6b1f73108546cb5f43fce
$_POST Array
[username] =>
[password] =>
[token] => 00a28586a1a89b30149ef130ca6f3c01a25435ad1b0ad1a19326205c75b80d79
[login] => Login
Third submit var_dump:
$_SESSION Array
[token] => 8be7ecbdae6274d1ba5ce9e8ace0af7c76e3e7d181c507d3da9b8c35652865cc
$_POST Array
[username] =>
[password] =>
[token] => e093e312b379d766d46083d616fa8655f1565dc19ed6b1f73108546cb5f43fce
[login] => Login
If you look carefully you can see the $_POST token is simply shifting downwards — becoming what $_SESSION was during the last submission.
It has me confused because $token and $_SESSION['token'] are only set once at the top of the page — they shouldn’t be different when the user clicks submit.
To summarize, $_SESSION contains the currently generated token, and $_POST contains the previously generated token.
Any ideas? Thank you!
The problem is that the session token is being changed before the login handling code, you need to move the token generating bit to happen after the login handling code.