はい、格納されたトークンのアプローチでは、生成されたすべてのトークンを、いつでも戻ってきた場合に備えて保持する必要があります。複数のブラウザー タブ/ウィンドウだけでなく、前後のナビゲーションでも、1 つの格納されたトークンが失敗します。一般に、古いトークンを期限切れにすることで、潜在的なストレージの爆発を管理する必要があります (それ以降に発行されたトークンの年齢および/または数によって)。
トークンの保存を完全に回避する別のアプローチは、サーバー側のシークレットを使用して生成された署名付きトークンを発行することです。その後、トークンが戻ってきたら、署名を確認できます。一致する場合は、署名したことがわかります。例えば:
// 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;
}
これにより、署名が一致するトークンを取得した場合、それを生成したことがわかります。それ自体はあまり役に立ちませんが、ユーザーIDなど、ランダム性以外の追加のものをトークンに入れることができます。
$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
現在、各フォームの送信は、フォームを取得した同じユーザーによって承認される必要があり、CSRF をほぼ無効にします。
トークンに入れることをお勧めするもう 1 つのことは、有効期限です。これにより、一時的なクライアントの侵害や MitM 攻撃によって、そのユーザーに対して永久に機能するトークンと、パスワードのリセット時に変更される値がリークされなくなります。パスワードを変更すると既存のトークンが無効になるようにします。