Sí, con el enfoque de tokens almacenados tendría que conservar todos los tokens generados por si volvían en cualquier momento. Un solo token almacenado no solo falla para múltiples pestañas/ventanas del navegador sino también para navegación hacia atrás/adelante. Por lo general, desea administrar la posible explosión de almacenamiento caducando tokens antiguos (por edad y/o cantidad de tokens emitidos desde entonces).
Otro enfoque que evita por completo el almacenamiento de tokens es emitir un token firmado generado utilizando un secreto del lado del servidor. Luego, cuando recuperes el token, puedes verificar la firma y, si coincide, sabes que la has firmado. Por ejemplo:
// Only the server knows this string. Make it up randomly and keep it in deployment-specific
// settings, in an include file safely outside the webroot
//
$secret= 'qw9pDr$wEyq%^ynrUi2cNi3';
...
// Issue a signed token
//
$token= dechex(mt_rand());
$hash= hash_hmac('sha1', $token, $secret);
$signed= $token.'-'.$hash;
<input type="hidden" name="formkey" value="<?php echo htmlspecialchars($signed); ?>">
...
// Check a token was signed by us, on the way back in
//
$isok= FALSE;
$parts= explode('-', $_POST['formkey']);
if (count($parts)===2) {
list($token, $hash)= $parts;
if ($hash===hash_hmac('sha1', $token, $secret))
$isok= TRUE;
}
Con esto, si obtienes un token con una firma coincidente, sabes que la has generado. Eso no es de mucha ayuda en sí mismo, pero entonces usted puede poner cosas extras en el token que no sea la aleatoriedad, por ID de usuario de ejemplo:
$token= dechex($user->id).'.'.dechex(mt_rand())
...
if ($hash===hash_hmac('sha1', $token, $secret)) {
$userid= hexdec(explode('.', $token)[0]);
if ($userid===$user->id)
$isok= TRUE
Ahora cada envío del formulario tiene que ser autorizada por el mismo usuario que recogió el forma, que prácticamente derrota a CSRF.
Otra cosa que es una buena idea poner un token es un tiempo de caducidad, por lo que un compromiso momentáneo del cliente o un ataque MitM no pierde un token que funcionará para ese usuario para siempre, y un valor que cambia al restablecer la contraseña, de modo que cambiar la contraseña invalide los tokens existentes.
¿Pero cómo hacer que funcione en todas las ventanas? –
Er, sí, esto funcionará globalmente. Varias pestañas y ventanas múltiples estarán bien. – bobince
@bobince No, la sesión se sobrescribirá? –