三つの名前を暗記する前に、一回の要求を区別する
アプリから外部サービスの認可画面へ進み、戻ってきた応答を処理するとき、その応答が自分の始めた取引に対応するかを確かめる必要があります。「ランダムな文字列があるから安全」と覚えるだけでは、どこで何を照合するかを説明できません。
ここでは、一回の認可要求Aと、それとは別の要求Bを記号で表します。実際の秘密値や通信ログは使いません。OAuthの認可コードフローを土台に、OpenID Connect(OIDC)でログインの情報を受け取る場合も区別します。資格の公式問題や、このサイトのログイン実装を再現したものではありません。
| 仕組み | 要求時に送る値 | 後で確認するもの | 主な確認側 |
|---|---|---|---|
| PKCE(S256) | verifierから計算したchallenge | トークン要求のverifierを変換した結果と、コードに結び付いたchallenge | 認可サーバー |
| state | クライアントが要求に結び付けた値 | 認可応答に返ったstateと、保持していた期待値 | クライアント |
| OIDCのnonce | 認証要求のnonce | IDトークンのnonceクレームと、要求時の値 | クライアント(IDトークンの検証時) |
最初は、①コード交換を認可サーバーが照合するのか、②戻ってきた応答をクライアントが照合するのか、③IDトークンのクレームをクライアントが検証するのか、の順に見分けます。challenge・verifier・state・nonceを同じ文字列にしてよい、という表ではありません。
RFC 9700の2.1節と4.7.1節は、認可サーバーの対応を確認したPKCEやOIDCのnonceによるCSRF対策も説明しています。このため「stateだけがCSRF対策、PKCEとnonceは無関係」と固定せず、適用するフローと前提条件を読みます。署名・発行者・受信者・有効期限などのトークン検証も省略できません。
PKCE:コードの取得と、交換に必要な証明を分ける
PKCEでは、クライアントが取引ごとにcode_verifierを生成し、そこから作ったcode_challengeを認可要求に含めます。後で認可コードをトークンへ交換するときにverifierを渡し、認可サーバーが対応関係を確認します。S256方式のchallengeは、verifierをSHA-256で処理し、Base64urlで符号化したものです。
図ではverifierをV、S256で得るchallengeをH(V)と略記します。H(V)は実際の仕様の符号化処理を含む説明用記号です。サーバーがPKCEを適切に強制しているなら、コードだけを得た第三者は、それに対応するverifierなしでは同じ交換を成功させられません。コードの横取り等への対策であり、利用者のあらゆる詐欺被害を防ぐ万能機能ではありません。
- 1
アプリ内
取引AのVを生成
- 2
認可要求
H(V)を渡す
- 3
コード交換
コードとVを渡す
- 4
サーバー
対応するか確認
Vはcode_verifier、H(V)はS256のchallengeを表す記号。実際の秘密値ではありません。
state:戻ってきた応答と、始めた要求を対応させる
stateは、要求とコールバックの対応を保つための値です。CSRF対策に使う場合、一回用で予測されにくい値を、安全に利用者のセッション等へ結び付け、戻ってきた値を検証します。「stateという項目が存在する」だけでは、この対応確認を行ったことになりません。
独自例で、要求Aに対応する値をS_A、別の要求BをS_Bとします。Aの応答を処理しているのにS_Bが返った場合、Aの要求に対応するという確認が取れていません。固定値を全員で使ったり、受け取った値を保存済みの状態と照合しなかったりすれば、この例で求めている対応付けが崩れます。
nonce:OIDCのIDトークンと、認証の取引を対応させる
OIDCのnonceは、クライアントのセッションをIDトークンに対応させ、リプレイ攻撃の緩和に使う値です。認証要求で送った場合、IDトークン内のnonceとの一致を検証します。stateの応答パラメータだけを確認して、トークン内のnonceも検証したことにはできません。
Aに対応するN_Aを期待しているのに、トークンにN_Bが入っていれば対応が取れません。また、一般のOAuthアクセストークンにnonceが必ず入る、と覚えるのも誤りです。アクセストークンとIDトークンは役割が異なります。利用するOIDCフローの仕様と、送ったパラメータに応じた検証条件を確認します。
値を受け取っただけ
- state欄がある
- IDトークンがある
- コードを取得できた
対応を検証する
- 期待する取引と照合
- nonce等の条件を検証
- verifierとの関係を確認
実際には署名・発行者・受信者・期限など、各仕様で必要な検証も行います。
| 要素 | 主に対応を確かめる対象 | 照合の場面 |
|---|---|---|
| PKCE | 認可コードとverifier | 認可サーバーでコード交換 |
| state | 開始した要求と戻った応答 | クライアントでコールバック処理 |
| OIDCのnonce | セッションとIDトークン | クライアントでIDトークン検証 |
CSRF対策の役割は重なる:三つの完全な分業ではない
「PKCEは横取りだけ、stateだけがCSRF、nonceは別のことだけ」と完全に切り分けると、現在のベストプラクティスを読み違えます。RFC 9700は、認可サーバーのPKCE対応を確かめたクライアントが、PKCEの提供するCSRF対策に依拠できる条件を示しています。OIDCのnonceにもCSRF対策の役割があります。
一方、PKCE対応の有無や正しい取引への結び付けを確かめず、「PKCEという名前があるからstateを削ればよい」とは言えません。RFC 9700の条件と、採用するフロー・ライブラリの仕様を確認します。本記事の三つの説明は照合対象を理解するためのもので、既存の対策を減らすための手順書ではありません。
PKCEはクライアント認証や、全トークン検証の代わりではない
verifierを提示できることと、秘密を保持できるクライアントとして認証されることは同じではありません。適用するクライアントの種類と仕様により、必要なクライアント認証等を別に考えます。PKCEを入れればIDトークンの署名や発行者を見なくてよい、ということにもなりません。
トークンの発行者、受信者、有効期限、署名など、利用するフローの検証を行います。TLS、リダイレクトURIの検証、複数の認可サーバーを扱う場合の取り違え対策なども別の確認です。認証・認可の失敗を調べるために、これらの検証を安易に無効化しないことが大切です。
別の取引の値が混ざったとき、どの段階で気付けるか
要求Aのコードに、B用のverifierを組み合わせる例では、PKCEの対応確認が焦点です。Aのコールバックで別のstateを受け取る例では、期待した要求との照合を見ます。Aの認証に対してBのnonceを持つIDトークンが渡る例では、IDトークンの検証が焦点です。いずれも架空の組合せで、実際の攻撃操作を試す必要はありません。
値の名前を当てるだけでなく、「誰が期待値を保持し、誰が何を検証するか」を言葉にしてみてください。認可コード、IDトークン、アクセストークンを同じ箱へ入れる説明になっていないかも確認します。誤った組合せを拒否することと、正しい権限を付与する判断を分けて考えられます。
関連する学習:鍵・認証方式・SaaS権限を混ぜない
署名や証明書の役割はPKIの基礎、パスキーの認証はパスキーとFIDO2で確認できます。いずれも重要ですが、認可要求への応答を対応付ける仕組みと、同じ意味の用語ではありません。
SaaSのOAuth権限管理では、どのアプリにどのアクセスを認めるかという運用面へつなげます。通信・認証の問題集や模試で、プロトコル上の照合と、利用者が認めたアクセス範囲を混ぜない練習をすると、単語の暗記から一歩進んだ理解になります。