どういうときに使うか
- 別のアプリが同じカスタム URI スキームを登録しているスマートフォン上のネイティブアプリ: 悪意あるアプリがリダイレクトと認可コードを受け取っても、
code_verifierを見ていないのでトークンエンドポイントは引き換えを拒否する (Section 1)。 - バイナリに client secret を埋め込んで配るネイティブアプリ: 仕様が挙げる攻撃の前提条件がまさにこれで、全インスタンスが同じ
client_idを使い、バイナリ内の secret は機密ではないので、攻撃者がトークンエンドポイントを呼べる (Section 1)。 - HTTP ログが漏れる OS で、攻撃者が認可レスポンスだけでなくリクエストも見られる場合:
plainではリクエスト内の challenge が verifier そのものなので、このケースを緩和するにはcode_challenge_methodをS256か別の暗号学的に安全なメソッドにしなければならない (Section 1)。 - 古い client を受け付け続けなければならないサーバー: サーバーは PKCE パラメータを送らない OAuth 2.0 client を引き続き受け付けてよいので、段階的に展開できる (Section 5)。
図
flowchart LR
A["public client がリダイレクトで code を受け取る"] -->|"PKCE なし: デバイス上の攻撃者がリダイレクトを横取り"| B["攻撃者が code を token に引き換える"]
A -->|"PKCE あり: code_challenge を先に送る"| C["攻撃者は code を持っていても code_verifier がなく、トークンエンドポイントは invalid_grant を返す"]解決する問題
認可コードグラントでは、code はユーザーエージェントを経由して client に戻る。デバイス上では、それを運ぶリダイレクトを別のアプリケーションが観察したり乗っ取ったりできる。仕様は前提条件を挙げている。デバイス上の悪意あるアプリが同じカスタム URI スキームを登録して認可レスポンスを観察できること、認可コードグラントが使われていること、攻撃者が client_id と (あれば) client_secret を持っていること。ネイティブアプリのバイナリに埋め込んだ secret は機密とは言えない (Section 1)。素の OAuth 2.0 には code をリクエストを始めたアプリケーションに結び付けるものがないので、code を持っている者なら誰でも引き換えられる。
PKCE はその結び付きを足す。フローを始めた client だけが code_verifier を知っているので、その client だけがフローを終えられる。
仕組み
図
sequenceDiagram
participant C as クライアント
participant AS as 認可サーバー
C->>C: code_verifier を生成 43〜128 文字の unreserved 文字
C->>C: code_challenge = code_verifier の SHA256 を BASE64URL
C->>AS: 認可リクエストに code_challenge と code_challenge_method=S256
AS->>AS: 発行する code に code_challenge と method を紐付けて保存
AS-->>C: code を付けてリダイレクト
C->>AS: トークンリクエストに code と code_verifier
AS->>AS: code_verifier の S256 を再計算し保存した code_challenge と比較
AS-->>C: access token、一致しなければ invalid_grantclient は 43〜128 文字の code_verifier を生成し (Section 4.1)、S256 メソッドなら BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) として code_challenge を導出する。plain なら verifier そのものを使う (Section 4.2)。認可リクエストに code_challenge と code_challenge_method を添えて送り、method を省略すると plain 扱いになる (Section 4.3)。認可サーバーは両方の値を発行する認可コードに紐付ける (Section 4.4)。トークンエンドポイントで client は code_verifier を加え (Section 4.5)、サーバーは code に紐付いた method で変換して保存した challenge と比較する (Section 4.6)。
仕様が求めること
code_verifierは高エントロピーのランダム文字列で、最小 43 文字、最大 128 文字 (Section 4.1)。- client が
S256を使えるならS256を使わなければならない。S256はサーバー側で実装必須 (Section 4.2)。 - 認可リクエストの
code_challengeは必須で、code_challenge_methodは省略時にplainになる (Section 4.3)。 - サーバーは
code_challengeとcode_challenge_methodを認可コードに紐付けなければならず (Section 4.4)、PKCE を要求しているのにcode_challengeが送られてこない場合や、要求されたメソッドをサポートしない場合はinvalid_requestを返さなければならない (Section 4.4.1)。 - トークンリクエストの
code_verifierは必須で、method は code 発行時に code に束縛される (Section 4.5)。 - 変換した verifier が保存した challenge と等しければトークンエンドポイントは通常どおり処理を続け、等しくなければ
invalid_grantを返す (Section 4.6)。
注意点
- client は
code_verifierを最低 256 ビットのエントロピーで作るべき (Section 7.1)。 plainは使うべきでなく、配備済み実装との互換のためだけに存在する。S256を試した後にplainへダウングレードしてはならない (Section 7.2)。code_challenge_methodがplainのときは Section 1 の横取り攻撃しか緩和されず、リクエストの盗聴者には効かない (Section 1)。- code challenge の生成に salt は使わないので、verifier 自身のエントロピーだけが防御になる (Section 7.3)。
- サーバーは PKCE を実装しない client を受け付けてよい (Section 5)。強制するかどうかは配備側の判断で、RFC が勝手に与えてくれるものではない。