OIDC / OAuth 2.0

stateパラメータとCSRF防止

セキュリティ

CSRF攻撃の仕組みと、stateパラメータによる防止方法を具体的なシナリオで理解しましょう。

CSRF攻撃とは何か

CSRF(Cross-Site Request Forgery)は、攻撃者が被害者のブラウザを利用して、被害者の意図しないリクエストを送信させる攻撃です。OAuthのフローにおいては、コールバックURLへのリダイレクトが特に狙われます。

具体的な攻撃シナリオ

攻撃者のアカウントを被害者に紐付ける攻撃(アカウント乗っ取り)

1

攻撃者が 正規のOAuthフローを開始し、認可サーバーでログインする

2

コールバックURLにリダイレクトされる直前で、認可コード付きのコールバックURLを記録して中断する

https://app.example.com/callback?code=ATTACKERS_CODE
3

このURLを被害者に踏ませる(メール、SNS、不正なWebページなど)

4

被害者のブラウザがコールバックURLにアクセスし、 アプリケーションが攻撃者の認可コードを使ってトークンを取得

5

被害者のセッションに攻撃者のアカウントが紐付けられる。攻撃者は自分のアカウントで被害者のデータにアクセスできる。

stateパラメータの役割

stateパラメータは、認可リクエストとコールバックを紐付けるためのランダムな値です。これにより、コールバックが自分が開始したフローの結果であることを確認できます。

1

クライアントがランダムな state 値を生成し、セッションに保存する

2

認可リクエストに state パラメータを含めて送信する

3

認可サーバーは state をそのままコールバックURLに付与して返す

4

クライアントは、返ってきた state とセッションに保存した値を比較し、一致しなければリクエストを拒否する

stateの生成と検証

state値の生成(推奨)

// 暗号的に安全なランダム値を生成
const state = crypto.randomUUID();

// セッションに保存
session.oauthState = state;

// 認可リクエストに含める
const authUrl = new URL("https://auth.example.com/authorize");
authUrl.searchParams.set("response_type", "code");
authUrl.searchParams.set("client_id", "my-app");
authUrl.searchParams.set("redirect_uri", "https://app.example.com/callback");
authUrl.searchParams.set("state", state);

コールバックでの検証

// コールバックで受け取ったstateを検証
function handleCallback(callbackParams, session) {
  const returnedState = callbackParams.get("state");
  const savedState = session.oauthState;

  // stateが一致しない場合はCSRF攻撃の可能性
  if (!returnedState || returnedState !== savedState) {
    throw new Error("state mismatch - possible CSRF attack");
  }

  // 使用済みのstateを削除(再利用防止)
  delete session.oauthState;

  // 認可コードの処理を続ける...
}
stateがない場合のリスク
  • 1.
    アカウント紐付け攻撃

    攻撃者のソーシャルアカウントが被害者のアプリアカウントに紐付けられ、 攻撃者がそのアカウントにアクセスできるようになる。

  • 2.
    セッション固定攻撃

    攻撃者が自分のセッションを被害者に使わせることで、 被害者の操作を攻撃者のアカウントで行わせる。

  • 3.
    ログインCSRF

    被害者が知らないうちに攻撃者のアカウントにログインさせられ、 被害者の行動が攻撃者のアカウントに記録される。

ベストプラクティス
  • -暗号的に安全なランダム値を使う(UUIDv4 や crypto.getRandomValues)
  • -state値は最低128ビットのエントロピーを持つ
  • -stateは一度使用したら破棄する(再利用しない)
  • -stateに有効期限を設定する(長時間放置されたフローは無効にする)
  • -SPAの場合、sessionStorageに保存する(タブごとに独立)

次のステップ

次は PKCEの仕組み で、認可コード横取り攻撃に対する防御策を学びましょう。