トークン方式のCSRF対策を図解:フォーム送信の安全化手順

Webサイトのフォーム送信には、トークンを使ったCSRF対策が欠かせません。
本記事では、CSRF対策におけるトークンの意味と有効な理由、トークンの発行・埋め込み・検証までの流れを図解で解説し、安全なフォーム送信を実現するための全体像を分かりやすく紹介します。
そもそもCSRFが何かよく知らないという方は別記事でCSRFの概要についても解説していますので、下記事も参考にしてみてください。
参考記事:【図解】CSRF(クロスサイトリクエストフォージェリ)とは?攻撃の流れと被害例を解説
目次
- 「トークン」とは?
- 図解で理解:CSRFトークンの仕組み(発行→埋め込み→検証)
- 実装パターン比較:セッション方式とDouble Submit Cookie方式
- PHPでの実装例:トークン発行と検証(最小構成)
- JavaScript/フロント側の実装:送信・読み取り・注意点
- CSRFトークンは盗まれますか?攻撃可能性と追加防御
- まとめ
「トークン」とは?
トークンとは、サーバが発行する一意な識別値です。
CSRF対策では、この値をフォーム送信時に一緒に送らせ、正規画面からの操作かどうかをチェックする仕組みとして使われます。
サーバ側で受信した値を保存済みのものと照合し、検証に失敗しました、トークンが無効ですといったエラーを返すことで不正操作を防ぎます。
図解で理解:CSRFトークンの仕組み(発行→埋め込み→検証)

手順1:トークン生成(推測困難なランダム値)
まずサーバ側で、毎回(または一定条件で)推測できないランダム文字列を作ります。図の 'abc123xyz' はその例となります。
この値は「そのフォームを開いた本人の画面からの送信かどうか」を見分けるための合言葉のような役割を持ちます。
手順2:トークンをフォームに埋め込む(hidden/ヘッダ)
次に、生成したトークンをユーザーが送信するフォームへ埋め込みます。
代表例が図のとおり、フォーム内に下記の様にのようにhidden項目として入れる方法です。
<input type="hidden" value="abc123xyz">
見えない入力欄なので、ユーザー操作なしでフォーム送信時に一緒に送られます。
手順3:フォーム送信時にトークンを送る(POSTが基本)
ユーザーが「送信」ボタンを押すと、フォームの入力値と一緒にトークンもサーバへ送られます(図の「POST送信」)。
CSRF対策では「状態を変える処理(購入・送金・変更・退会など)はPOSTで」がお約束で、POST本文やヘッダにトークンを含めて送ることで、サーバが後で正当性チェックをできます。
手順4:サーバでトークンをチェック(照合)して拒否/許可
最後にサーバ側で、送られてきたトークンを照合(チェック)します。
図のように、生成しているトークンが来たら許可(OK)、生成していないトークンが来たら拒否(NG)となります。
ポイントは「サーバが“正しい値”を知っている」ことです。多くの実装では、サーバはトークンをセッションに保存しておき、受信したトークンと一致するかを見ます。
実装パターン比較:セッション方式とDouble Submit Cookie方式
CSRF対策の「トークン方式」には代表的にセッション方式とDouble Submit Cookie方式があります。
どちらも「正しいトークンを送れたリクエストだけ通す」点は同じですが、トークンの保管場所と照合方法が違います。
セッションに保存する方式
サーバ側でCSRFトークンを生成し、ユーザーのセッションに保存して管理する方式です。
フォーム表示時にトークンをhiddenなどで埋め込み、送信時に受け取ったトークンとセッション内の値を照合して一致すれば許可します。
Double Submit Cookie方式
サーバはCSRFトークンを生成してCookieに保存し、同じ値をフォームのhiddenやリクエストヘッダ(例:X-CSRF-Token)にも載せて送信させます。
受信側では「Cookie内のトークン」と「送信されたトークン」を一致チェックし、一致すれば許可します。
PHPでの実装例:トークン発行と検証(最小構成)
<?php session_start(); // セッションを開始(サーバ側にトークンを保存して照合するため) if ($_SERVER['REQUEST_METHOD'] === 'POST') { // フォーム送信(POST)時だけ検証処理を行う $token = $_POST['csrf_token'] ?? ''; // 送られてきたCSRFトークンを取り出す(無ければ空文字) if (!hash_equals($_SESSION['csrf_token'] ?? '', $token)) { // セッションに保存した正しいトークンと一致するか比較(タイミング攻撃対策) http_response_code(403); // 不正リクエストとして403(Forbidden)を返す exit('検証に失敗しました'); // ここで処理を中断(トークン不一致=CSRFの可能性) } unset($_SESSION['csrf_token']); // 検証成功後にトークンを消して再利用を防ぐ(ワンタイム化) exit('OK'); // 正常処理(本来はここで更新処理などを実行する) } $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); // 初回表示(GET等)時に推測困難なランダム値を生成してセッションへ保存 ?> <form method="post"> <!-- POSTで送るのが基本(状態変更の操作に使う) --> <input type="hidden" name="csrf_token" <!-- ユーザーには見えないhiddenでトークンをフォームに埋め込む --> value="<?= htmlspecialchars($_SESSION['csrf_token'], ENT_QUOTES) ?>"> <!-- トークンをHTMLに出すのでエスケープして安全に埋め込む --> <button type="submit">送信</button> <!-- 送信ボタン --> </form>
PHPでトークンの処理を実装する場合、上記の様にフォームを表示するタイミングで推測困難なCSRFトークンを発行し、PHPのセッションに保存します。
フォームにはhiddenで埋め込み、送信時に受信側で「セッションに保存した値」と「受け取った値」を照合します。
一致しなければ改ざんの可能性があるため403で拒否し、hash_equalsで安全に比較します。成功後はトークンを破棄して使い回しを防ぎます。
JavaScript/フロント側の実装:送信・読み取り・注意点
<meta name="csrf-token" content="abc123xyz"> <!-- CSRFトークンをHTML内に埋め込む。サーバが発行した値を入れる(例では固定値だが実運用では毎回生成) --> <script> <!-- ここからJavaScript。トークンを読み取り、リクエストに載せて送る --> const token = document.querySelector('meta[name="csrf-token"]').content; <!-- metaタグをCSSセレクタで探し、content属性のトークン文字列を取得 --> fetch('/api/update', { <!-- /api/update に対して非同期でHTTPリクエストを送る(フォーム送信ではなくAJAX/SPA向け) --> method: 'POST', <!-- 状態変更はPOSTが基本。GETにするとリンク踏ませで実行されやすく危険 --> headers: { 'Content-Type': 'application/json', 'X-CSRF-Token': token }, <!-- JSON送信を宣言し、独自ヘッダX-CSRF-Tokenにトークンを付ける(サーバ側で照合する) --> body: JSON.stringify({ name: 'taro' }) <!-- 送るデータ本体。オブジェクトをJSON文字列に変換して送信 --> }).then(r => { if (!r.ok) throw new Error('検証に失敗しました'); }); <!-- レスポンスが2xx以外なら失敗扱いにしてエラー化(例:403など) --> </script> <!-- JavaScript終了 -->
JavaScript側では、フォームに埋め込まれたCSRFトークンを「読み取り→送る」だけですが、SPAやAJAXでは送信先や更新タイミングで事故が起きやすいです。
通常フォームならhiddenの値が一緒に送られます。fetchでAPIに送る場合は、上記コードのようにHTMLの<meta>やhiddenからトークンを取得し、X-CSRF-Tokenなどのヘッダに付けてPOSTします。
戻る操作やキャッシュで古いトークンが残ると「CSRFトークンが無効です」になりやすいので、画面遷移なしの設計では再取得の導線も用意します。
CSRFトークンは盗まれますか?攻撃可能性と追加防御
CSRFトークンは「推測されにくい値」ですが、絶対に盗まれない秘密鍵というより「正規フォーム経由かを判定する目印」です。
基本は第三者サイトから勝手に送信されてもトークンが一致せず防げますが、もしXSSがあると、攻撃者がページ内でJSを動かしてトークンを読み取り、正規の送信と同じ形で突破される可能性があります。
補強として**SameSite Cookie(Lax/Strict)**でクロスサイト送信を減らし、Origin/Refererチェックを併用するのが有効です。
さらに送金・退会などは再認証や確認画面、レート制限で被害を小さくします。
まとめ
ここまでの内容をまとめると下記の通りです。
- トークンとは、サーバが発行する一意な識別値
- トークンの基本的な仕組みは、発行→埋め込み→検証
- トークン方式にはセッションに保存するセッション方式とcookieに保存するDouble Submit Cookie方式がある
- トークンはCSRF攻撃では盗まれない前提で設計されているが、XSS攻撃などで盗まれる可能性もあるのでさらなる補強は必要
トークンを実装していない方は、是非実装してセキュリティを高めていきましょう。





