OAuth 2.0 の認可フロー内部(Authorization Code + PKCE)
PKCE が code interception を、state/nonce が CSRF とリプレイを別々に塞ぐ原理を内部から理解し、Authorization Code + PKCE の選定と実装を誤らない。
- 1.OAuth 2.0 はアクセストークンを「誰に・どう渡すか」のフロー集。Authorization Code はトークンを直接ブラウザに晒さず、認可コードをバックチャネルで交換するため最も安全。Implicit はトークンを URL フラグメントで直接返すため非推奨化された。
- 2.PKCE は code_verifier(高エントロピー乱数)の SHA-256 ハッシュを code_challenge として先に送り、トークン交換時に元の verifier を提示させる。傍受された認可コードだけでは verifier を再現できず、code interception 攻撃が成立しない。
- 3.state は認可リクエスト〜コールバックを紐付ける乱数で CSRF(強制ログイン)を防ぐ。nonce は ID トークンに埋め込ませる乱数で、古いトークンの使い回し(リプレイ)を検出する。両者は役割が別で併用する。
OAuth 2.0 は「トークンの渡し方」の規格
OAuth 2.0(RFC 6749)は、ユーザーのパスワードを第三者アプリに渡さずに、限定された権限(スコープ)を表すアクセストークンを安全に発行・委譲するための枠組みです。登場人物は4者あります。
- リソースオーナー:本人(ユーザー)。
- クライアント:トークンを使いたいアプリ(SPA・モバイル・サーバー)。
- 認可サーバー:本人を認証し、コードやトークンを発行する。
- リソースサーバー:トークンを検証して API を提供する。
OAuth 2.0 が**認可(Authorization)の規格である点に注意が必要です。「この人が誰か」を確実に伝える認証(Authentication)**は OAuth 2.0 単体の責務ではなく、その上に OpenID Connect(OIDC) を載せて ID トークンを得ます。両者の役割分担は 認証と認可の違い の通りで、OAuth でログインを実装するときは OIDC を併用するのが正道です。
「フロー(グラントタイプ)」とは、この4者の間でどの経路を通ってトークンを受け渡すかの手順の違いにすぎません。経路が違えば、傍受や CSRF に対する強度も変わります。
4つのフローの違い
代表的な4フローを、用途と安全性の観点で並べます。
| フロー | 主な用途 | トークンの渡り方 | 現在の評価 |
|---|---|---|---|
| Authorization Code (+PKCE) | Webアプリ・SPA・モバイル全般 | 認可コードをバックチャネルでトークンに交換 | 推奨(事実上の標準) |
| Implicit | 旧来のSPA | トークンをURLフラグメントで直接返す | 非推奨(PKCE付きCodeへ移行) |
| Client Credentials | サーバー間(ユーザー不在) | クライアント認証情報を直接トークンに交換 | 用途が合えば適切 |
| Device Authorization | TV・CLI等の入力困難な端末 | 別端末でコード入力、本体はポーリング | 該当端末で適切 |
- Authorization Code:認可サーバーはまず短命の認可コードを返し、クライアントは**サーバー間の直接通信(バックチャネル)**でそのコードをトークンに交換します。トークンがブラウザの URL やフロントチャネルに露出しないのが安全性の核です。
- Implicit:コード交換を省き、
#access_token=...の形でトークンをフラグメントに直接返していました。ブラウザ履歴・Referer・拡張機能に漏れやすく、リフレッシュトークンも安全に扱えないため、現在は PKCE 付き Authorization Code への一本化が勧告されています。 - Client Credentials:ユーザーが介在しないバッチやマイクロサービス間で、クライアント自身の
client_id/client_secretを提示してトークンを得ます。人間の同意画面が無い点が他と決定的に違います。 - Device Authorization:キーボードの無いテレビや CLI 向け。本体は
device_codeを取得して画面に短いユーザーコードを表示し、ユーザーはスマホ等の別端末で承認、本体はトークン発行をポーリングで待ちます。
PKCE が code interception を防ぐ原理
Authorization Code の弱点は、認可コードがリダイレクトでブラウザを経由することです。とくにモバイルや SPA では client_secret を秘匿できないため、悪意あるアプリが OS のカスタム URL スキームを横取りするなどして認可コードを傍受すると、それをトークンに交換できてしまいます。これが code interception(認可コード傍受)攻撃です。
PKCE(Proof Key for Code Exchange、RFC 7636) は、コードを横取りされても交換できなくする仕組みです。要は「コードを開始した本人だけが知る秘密」を後出しで証明させます。
1. クライアントが乱数を生成: code_verifier (43〜128文字の高エントロピー文字列)
2. その SHA-256 を Base64URL: code_challenge = BASE64URL(SHA256(code_verifier))
3. 認可リクエストに同梱: code_challenge と code_challenge_method=S256 を送る
→ 認可サーバーは challenge を認可コードに紐付けて記憶
4. コールバックで認可コード受領
5. トークン交換時に提示: 元の code_verifier を送る
→ サーバーは SHA256(verifier) == 記憶した challenge を検証。一致しなければ拒否
攻撃者が認可コードを傍受しても、code_verifier は最初のリクエストを送った正規クライアントのメモリ内にしか存在しません。通信路に流れるのはハッシュ済みの code_challenge だけで、SHA-256 は一方向関数なので challenge から verifier を復元できません。よって傍受したコードを交換しようとしても verifier を提示できず、サーバーが弾きます。
PKCE には code_challenge_method=plain(challenge と verifier が同一)も規格上ありますが、これは challenge がそのまま秘密値になるため傍受で破られます。実装では必ず S256(SHA-256 ハッシュ) を使ってください。RFC 7636 も S256 を必須・plain を非推奨としています。
PKCE は当初モバイル向けでしたが、現在は public client(secret を持てない SPA・モバイル)だけでなく confidential client でも全フローに付けるべきとされ、OAuth 2.1 のドラフトでは Authorization Code に PKCE が常時必須化されています。
state による CSRF 対策
PKCE は「コードの横取り」を防ぎますが、**「攻撃者が用意したコードを被害者に握らせる」**別系統の攻撃には別の対策が要ります。
攻撃者が自分のアカウントで認可を開始し、得たコールバック URL を罠ページから被害者のブラウザに踏ませると、被害者のクライアントセッションに攻撃者のアカウントが結び付く(ログイン CSRF/アカウント混同)危険があります。これを防ぐのが state パラメータです。
1. 認可リクエスト直前にクライアントが乱数 state を生成し、セッションに保存
2. 認可リクエストに state=<乱数> を付けて送る
3. 認可サーバーはコールバックで同じ state をそのまま返す
4. クライアントは「返ってきた state == セッションに保存した state」を検証
→ 一致しなければ、自分が始めたフローではないとみなし破棄
state は認可開始とコールバックが同一ブラウザ・同一セッション由来であることを保証する CSRF トークンそのものです。仕組みは CSRF(クロスサイトリクエストフォージェリ) のトークン照合と同型で、推測不能な乱数を「往復させて突き合わせる」点が共通します。state には戻り先 URL を持たせることもありますが、その場合も改ざん検知できる形で署名/照合する必要があります。
ライブラリ任せにせず、state を生成・保存・照合しているかを必ず確認してください。検証を省くと、ログイン CSRF や、悪意あるサイトへトークンを送らせる攻撃の入口になります。PKCE があっても state の役割は代替できません(守る対象が別だからです)。
nonce によるリプレイ対策(OIDC)
state が OAuth のフロー全体を守るのに対し、nonce は OIDC の ID トークンを守る値です。役割を取り違えないよう、両者を整理します。
| 値 | 守る対象 | 防ぐ攻撃 | 検証の仕方 |
|---|---|---|---|
| state | 認可リクエスト〜コールバックの往復 | CSRF・ログイン強制・アカウント混同 | 送った値と返った値の一致をクライアントが照合 |
| nonce | ID トークン(OIDC) | ID トークンのリプレイ(使い回し) | 送った値が ID トークンの nonce クレームと一致するか照合 |
| PKCE | 認可コード | code interception(コード傍受) | verifier のハッシュが challenge と一致するか認可サーバーが照合 |
OIDC では認可リクエストに nonce=<乱数> を載せます。認可サーバーは発行する ID トークンの nonce クレームに同じ値を埋め込んで署名します。クライアントは ID トークン検証時に「自分が送った nonce と一致するか」を確認します。これにより、過去に発行された ID トークンを攻撃者が再注入してもセッションを取れません——古いトークンの nonce は現在のリクエストと一致しないからです。
認可リクエスト: ...&nonce=Xk29...&...
ID トークン(JWT) のペイロード例:
{
"iss": "https://issuer.example",
"aud": "client-123",
"nonce": "Xk29...", ← 送った nonce と一致するか検証
"exp": 1750000000, ← 期限切れも必ず確認
"iat": 1749996400
}
ID トークンの検証では nonce に加えて iss(発行者)・aud(自分宛か)・exp(期限)・署名をすべて確認します。ここを緩めると、別クライアント宛のトークンの流用や期限切れトークンの受理を許します。フィッシング耐性まで踏み込むなら、認証要素そのものを公開鍵チャレンジにする FIDO2 / WebAuthn や、second factor の TOTP/HOTP と組み合わせる設計が有効です。
どのフローを選ぶか
判断は「ユーザーが介在するか」「secret を秘匿できるか」「端末が入力可能か」の3点でほぼ決まります。
| 状況 | 選ぶフロー | 必須の付帯対策 |
|---|---|---|
| SPA / モバイル(secret 不可) | Authorization Code + PKCE | PKCE(S256) + state、ログインなら nonce |
| サーバーサイド Web(secret 可) | Authorization Code + PKCE | PKCE + state + client_secret |
| ユーザー不在のサーバー間 | Client Credentials | client認証情報の厳格な秘匿・スコープ最小化 |
| TV・CLI 等の入力困難端末 | Device Authorization | user_code の有効期限・ポーリング間隔遵守 |
| 旧来の Implicit を使用中 | Code + PKCE へ移行 | フラグメント返却の廃止 |
結論として、ユーザーが関わるフローは例外なく Authorization Code + PKCEを選び、state を必ず照合し、ログインを伴うなら OIDC の nonce を加える——これが現行の正解です。Implicit は新規採用せず、既存実装も移行対象です。PKCE がコード傍受を、state が CSRF を、nonce がリプレイを、それぞれ別々の角度から塞いでいる点を理解しておけば、ライブラリ設定の意味も、監査で何を見るべきかも明確になります。
セキュリティ Article
OAuth 2.0 の認可フロー内部(Authorization Code + PKCE)を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
OAuth
比較で見る軸
難易度: advanced / カテゴリ: セキュリティ / タグ数: 6
導入後に効く点
PKCE は code_verifier(高エントロピー乱数)の SHA-256 ハッシュを code_challenge として先に送り、トークン交換時に元の verifier を提示させる。傍受された認可コードだけでは verifier を再現できず、code interception 攻撃が成立しない。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- セキュリティ
- タグ数
- 6
判断チェックリスト
- 自社の用途が「OAuth / PKCE」に近いか確認する。
- 強みである「OAuth 2.0 はアクセストークンを「誰に・どう渡すか」のフロー集。Authorization Code はトークンを直接ブラウザに晒さず、認可コードをバックチャネルで交換するため最も安全。Implicit はトークンを URL フラグメントで直接返すため非推奨化された。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。