stateパラメータによるCSRF攻撃の防止
OAuth 2.0のstateパラメータがクロスサイトリクエストフォージェリ攻撃とログインCSRFをどのように防ぐか。実装ガイドと例付き。
Security
詳細な説明
stateパラメータとCSRF保護
stateパラメータは、認可フロー中のクロスサイトリクエストフォージェリ(CSRF)攻撃を防ぐOAuth 2.0の重要なセキュリティ対策です。これがないと、攻撃者はユーザーに被害者のセッションで攻撃者のアカウントを認可させることができます。
stateなしの攻撃
- 攻撃者が被害者アプリケーションでOAuthフローを開始し、認可コードを取得
- 攻撃者がURLを構築:
https://victim-app.com/callback?code=ATTACKER_CODE - 攻撃者が被害者にリンクをクリックさせる(メール、フォーラム投稿など)
- 被害者のブラウザが被害者アプリにリクエストを送信
- 被害者アプリが攻撃者のコードをトークンに交換し、攻撃者の外部アカウントを被害者のセッションにリンク
- 攻撃者が被害者のアカウントにアクセス可能に
stateがこれを防ぐ方法
- リダイレクト前: 暗号学的にランダムな文字列を生成してセッションに保存:
const state = crypto.randomUUID();
sessionStorage.setItem("oauth_state", state);
// 認可URLにstateを含める
- コールバック時: stateが一致することを検証:
const urlParams = new URLSearchParams(window.location.search);
const returnedState = urlParams.get("state");
const storedState = sessionStorage.getItem("oauth_state");
if (returnedState !== storedState) {
throw new Error("State不一致 — CSRF攻撃の可能性");
}
sessionStorage.removeItem("oauth_state");
// トークン交換を続行
stateのベストプラクティス
| プラクティス | 理由 |
|---|---|
crypto.randomUUID()または32バイト以上のランダム値を使用 |
推測を防止 |
| セッションに保存(localStorageではなく) | セッションとともに有効期限切れ |
| 一回限りの使用 | リプレイ攻撃を防止 |
| すべての認可リクエストに含める | 多層防御 |
上級編:stateへのデータ埋め込み
追加情報(リターンURLなど)をエンコードして埋め込むことができます:
const statePayload = JSON.stringify({
nonce: crypto.randomUUID(),
returnTo: "/dashboard",
});
const state = btoa(statePayload);
コールバック時にデコードしてnonceを検証し、エンコードされたreturnTo URLにリダイレクトします。
ユースケース
認可コードまたは認可コード + PKCEフローを実装するすべてのWebアプリケーション。stateパラメータはCSRFおよびログインCSRF攻撃からユーザーを保護するために、すべての認可リクエストに含める必要があります。