フォームで2重送信が起きる可能性があります。
リダイレクトとトークン生成とそれぞれ2重送信防止の方法があります。
リダイレクト
ユーザーがフォームを送信(POST)した後、処理が完了したら別のページにリダイレクト(GETリクエスト)します。
メリット
ブラウザの更新ボタンを押してもフォームが再送信されることはありません。
デメリット
これだけでは、ユーザーがフォームを最初に送信する前に、フォームを複数回送信するのを防ぐことはできません。
トークン生成
一意のトークンをフォームに埋め込み、サーバーでトークンが既に使用されていないかをチェックします。
メリット
フォームが複数回送信されるのを防ぎます。ユーザーがフォームを最初に送信する前に複数回の送信を防止できます。
デメリット
ユーザーがフォームを送信した後、ブラウザを更新すると、フォームの内容が再送信される可能性があります。
リダイレクトとトークン生成を合わせる
総合的には、2重送信を防止するためには、これらの方法を組み合わせて用いることが最も効果的です。それにより、フォームの再送信による潜在的な問題を大幅に減少させることができます。
コード全体像
<?php
session_start();
// トークン生成
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// フォーム処理(POSTの場合)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// トークンチェック
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die("不正なリクエストです。");
}
//ここで処理を行う
// 処理後のリダイレクト
header('Location: complete.php');
exit;
}
?>
<!-- フォームのHTML -->
<form method="post" action="">
<!-- CSRFトークン埋め込み -->
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
<!-- フォームの要素 -->
<input type="submit" value="送信">
</form>
complete.php
<?php
// トークンをリセット
session_start();
unset($_SESSION['csrf_token']);
// 成功メッセージ等を表示
echo "処理が完了しました。";
説明
<?php
session_start();
// トークン生成
if (!isset($_SESSION['csrf_token'])) {
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}
// フォーム処理(POSTの場合)
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
// トークンチェック
if (!isset($_POST['csrf_token']) || $_POST['csrf_token'] !== $_SESSION['csrf_token']) {
die("不正なリクエストです。");
}
//ここで処理を行う
// 処理後のリダイレクト
header('Location: complete.php');
exit;
}
?>
<!-- フォームのHTML -->
<form method="post" action="">
<!-- CSRFトークン埋め込み -->
<input type="hidden" name="csrf_token" value="<?php echo htmlspecialchars($_SESSION['csrf_token']); ?>">
<!-- フォームの要素 -->
<input type="submit" value="送信">
</form>
トークンを生成し変数化する。
HTML側のinputに変数を渡す。
HTMLフォームからPOSTされたトークンを受け取り、行いたい処理をする。
処理が終わったらリダイレクトする。
<?php
// トークンをリセット
session_start();
unset($_SESSION['csrf_token']);
// 成功メッセージ等を表示
echo "処理が完了しました。";
一連の処理が終わったら、complete.phpへ移動する。
最後にunsetでセッションを終わらせる。