- Published on
OAuth 2.0 & OIDC Deep Dive — Authorization Code, PKCE, JWT, DPoP, FAPI 完全ガイド (2025)
- Authors

- Name
- Youngju Kim
- @fjvbn20031
TL;DR
- OAuth 2.0は**認可(authorization)**プロトコルであり、認証(authentication)ではない。「このアプリに自分のGmailを読ませる」という委譲であって、「自分が誰であるか」の証明ではない。
- OpenID Connect (OIDC)はOAuth 2.0の上に認証レイヤーを追加。ID Token (JWT)を発行して「ユーザーが誰であるか」を表現。
- 4つのflow: Authorization Code (+ PKCE)、Client Credentials、Device Code、Refresh Token。ImplicitとPasswordはOAuth 2.1で廃止。
- PKCEは当初モバイル公開clientのための拡張だったが、OAuth 2.1ではすべてのclientで必須。Authorization Code傍受攻撃の防御の中核。
- JWT: Base64URLエンコードされた
header.payload.signature。self-containedなトークン。検証は高速だが取り消しは困難。 - OIDC Discovery:
/.well-known/openid-configurationエンドポイントがすべてのURL/鍵/アルゴリズムを広告。clientが自動設定。 - JWKS: 署名公開鍵をJSONで配布。鍵ローテーション可能。
- Token binding: トークンを特定のclient/TLS接続に紐付ける。DPoP(proof-of-possession)、mTLS binding。
- FAPI: 金融グレードのセキュリティプロファイル。PAR、JARMなど追加保護。
1. OAuthの誕生
1.1 暗黒時代 (2000年代初頭)
2006年の方式:
アプリ: "Gmailのパスワードを教えてください。"
ユーザー: "..."
アプリ: GmailにIMAPログイン → 受信箱を読む。
問題点: アプリがパスワードを永久保存、権限分離なし、取り消し困難、2FA不可。
1.2 OAuth 1.0 (2007)
Twitter、Flickrなどが開発した権限委譲プロトコル。4つの役割: Resource Owner、Client、Resource Server、Authorization Server。ユーザーがASで承認→ASがclientにトークンを発行→clientはパスワードの代わりにトークンでAPIを呼ぶ。
しかしOAuth 1.0は複雑だった(リクエストごとにHMAC署名)。
1.3 OAuth 2.0 (2012)
RFC 6749。署名の代わりにTLS、トークンはbearer token、用途別に複数のflow。単純化の代償: セキュリティ責任が実装者に転嫁。正しく使えば安全だが、誤用しやすい。
1.4 OIDC (2014)
OAuthは認可プロトコルだが、人々は認証に使った(「Facebookでログイン」)。問題: access tokenを受け取っても「このユーザーが本当にFacebookにログインした」という証明ではない。
OpenID Connectが埋めた: OAuth 2.0の上にID Tokenを追加。署名されたJWTで「user@example.comが時刻TにAuth Serverにログインした」を証明。
1.5 OAuth 2.1 (ドラフト、2020+)
OAuth 2.0の10年の運用経験を反映:
- Implicit flow削除: セキュリティ問題。
- Password grant削除: clientがパスワードに触れること自体がOAuthに反する。
- PKCE必須: すべてのflowで必要。
- Redirect URI exact match: wildcard禁止。
- Refresh token rotation: 使用ごとに交換。
2. OAuth 2.0の基本概念
2.1 4つの役割
- Resource Owner (RO): リソースを所有するユーザー。
- Client: リソースにアクセスしたいアプリ。
- Resource Server (RS): APIサーバー。
- Authorization Server (AS): トークン発行サーバー。
RSとASは同じ組織の場合も別の場合もある。Googleは両方運用、Auth0はASのみ提供。
2.2 Clientの種類
- Confidential Client: 秘密を安全に保管できる(サーバーサイドWebアプリ)。Client Secret使用可能。
- Public Client: 保管できない(モバイルアプリ、SPA、CLI)。Secretなし → PKCEで補完。
OAuth 2.1ではほぼ全ケースでPKCEを使用。
2.3 トークンの種類
- Access Token: RSへのアクセス。短命(5〜60分)。
- Refresh Token: Access Token再発行用。長命(数日〜数ヶ月)。ASにのみ提出。
- ID Token (OIDCのみ): ユーザー身元情報。JWT。Clientが直接解析・検証。
2.4 Scope
権限範囲。scope=read:email write:calendar profile openid。openid scopeはOIDC使用を意味し、ID Tokenも発行される。
3. Authorization Code Flow
最も重要なflow。WebアプリのLogin標準。
3.1 概要
User ←→ Client ←→ Authorization Server ←→ Resource Server
1. User: "アプリでログインして"
2. Client: UserをASにリダイレクト
3. AS: ログイン画面表示
4. User: ログイン + 承認
5. AS: authorization codeをclientに返す (redirect)
6. Client: codeをtokenに交換 (サーバー間直接通信)
7. Client: access tokenでRS呼び出し
3.2 詳細フロー
Authorization Request:
GET /authorize?
response_type=code
&client_id=abc123
&redirect_uri=https://app.example.com/callback
&scope=openid profile email
&state=xyz789
&code_challenge=<PKCE>
&code_challenge_method=S256
Authorization Response:
https://app.example.com/callback?
code=AUTH_CODE_XYZ
&state=xyz789
Clientはstateを検証(CSRF防御)。
Token Request:
POST /token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&code=AUTH_CODE_XYZ
&redirect_uri=https://app.example.com/callback
&client_id=abc123
&client_secret=secret456
&code_verifier=<PKCE>
Token Response:
{
"access_token": "eyJhbGciOiJSUzI1NiIs...",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "def456...",
"id_token": "eyJhbGciOiJSUzI1NiIs...",
"scope": "openid profile email"
}
API呼び出し:
GET /api/userinfo
Authorization: Bearer eyJhbGciOiJSUzI1NiIs...
3.3 なぜこれほど複雑なのか
Front-channel/Back-channel分離: codeはブラウザ経由、tokenはサーバー間直接HTTP。tokenがブラウザに露出しない。ユーザー対話分離: ログイン・同意はAS画面、token使用はclientサーバー処理。Clientはユーザーパスワードを知る必要がない。
4. PKCE — 現代OAuthの必須
4.1 問題
モバイルアプリやSPAはclient secretを安全に保管できない。バイナリをデコンパイルすればsecretが露出。
攻撃シナリオ: 悪意のあるアプリが同じcustom URL schemeを登録 → AS authorization codeのredirectを傍受 → codeをtokenに交換 → アカウント乗っ取り。Secretがないためclientを証明できない。
4.2 PKCEの解決
PKCE (Proof Key for Code Exchange, RFC 7636)。発音: "ピクシー(pixy)"。
アイデア: clientがランダムな秘密値を生成し、(1) ハッシュを事前にASに送信、(2) token要求時に元の値を証明。
4.3 動作
code_verifier生成 (43〜128文字ランダム)。code_challenge = BASE64URL(SHA256(code_verifier))。- Authorization requestに
code_challenge含める。 - ASがcode発行時に
code_challengeを記憶。 - Token requestに元の
code_verifier含める。 - AS検証:
SHA256(code_verifier) == code_challenge?
一致すればtoken発行。
4.4 攻撃防御
悪意のあるアプリがcodeを傍受してもcode_verifierを知らない(元は合法なアプリのメモリ内)。Token要求が失敗。
4.5 Plain vs S256
- plain: ハッシュなし(使用禁止)。
- S256:
SHA256(code_verifier)(必須)。
4.6 OAuth 2.1: すべてのclientで必須
Defense in depth: secretが漏洩してもPKCEが追加防御線。
5. JWT — JSON Web Token
5.1 構造
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIn0.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
│ Header │ Payload │ Signature │
各部分はBase64URLエンコードされたJSON。
Header:
{ "alg": "HS256", "typ": "JWT", "kid": "my-key-id" }
Payload (Claims):
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1700000000,
"exp": 1700003600,
"iss": "https://example.com",
"aud": "my-api"
}
標準claim: sub(Subject)、iss(Issuer)、aud(Audience)、exp(Expiration)、iat(Issued At)、nbf(Not Before)、jti(JWT ID)。
5.2 署名アルゴリズム
- HS256: HMAC-SHA256。対称鍵。
- RS256: RSA-SHA256。非対称。
- ES256: ECDSA P-256。非対称、より小さい署名。
- EdDSA: Ed25519。最速・最新。
- none: 本番絶対禁止。
OIDCは通常**非対称(RS256/ES256)**を使用 — ASが署名、client/RSが公開鍵で検証。
5.3 "alg: none"脆弱性
初期のJWTライブラリが{"alg":"none"}.{"admin":true}.のような署名なしJWTを受け入れた。対策: 検証時に期待するアルゴリズムをハードコード、algを信頼しない。
5.4 Key Confusion
サーバーがRS256の公開鍵を持つ。攻撃者がalg: HS256に変更し、公開鍵をHMAC秘密として署名。ライブラリがalgを信頼すると偽造通過。対策: アルゴリズム固定。
5.5 JWTの長所と短所
長所: self-contained、stateless、標準化。短所: 取り消し困難(expまで有効)、サイズ(毎リクエスト送信)、payloadは署名されるが暗号化されない(誰でも見える)。
実務: 短命access token (5〜15分) + refresh token。
6. OIDC — 認証レイヤー
6.1 OAuthとの違い
OAuthは認可、OIDCは認証。OAuthだけだとaccess tokenがあっても「誰がログインしたか」公式には分からない。OIDCの解決: ID Token。
6.2 ID Token vs Access Token
| 項目 | ID Token | Access Token |
|---|---|---|
| 目的 | ユーザー身元証明 | APIアクセス |
| 受信者 | Client | Resource Server |
| 形式 | JWT (必須) | JWT または opaque |
| 誰が検証 | Client | Resource Server |
| 転送先 | Clientメモリ (HTTP要求に載せない) | Authorizationヘッダー |
重要: ID TokenをAPI呼び出しに使うな。Access Tokenを身元確認に使うな。
6.3 ID Token例
{
"iss": "https://accounts.google.com",
"sub": "110169484474386276334",
"aud": "1234.apps.googleusercontent.com",
"iat": 1700000000,
"exp": 1700003600,
"email": "user@example.com",
"email_verified": true,
"name": "John Doe"
}
6.4 Nonce
ID token再生攻撃防止。Clientがauthorization requestにnonce=<random>を追加 → ASがID Tokenのnonce claimに同じ値を含める → Client検証時に一致確認。
6.5 UserInfo Endpoint
GET /userinfo
Authorization: Bearer <access_token>
7. OIDC DiscoveryとJWKS
7.1 Discovery
GET https://accounts.google.com/.well-known/openid-configuration
レスポンス:
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "...",
"token_endpoint": "...",
"userinfo_endpoint": "...",
"jwks_uri": "...",
"id_token_signing_alg_values_supported": ["RS256"]
}
Clientはissuer URLだけ知っていればすべて自動設定。
7.2 JWKS
{
"keys": [
{ "kid": "key-1", "kty": "RSA", "alg": "RS256", "use": "sig", "n": "...", "e": "AQAB" }
]
}
検証者はJWT headerのkidでキーを特定し、そのキーで署名検証。
7.3 Key Rotation
Day 1: [A]
Day 30: [A, B] # B追加、Aで署名された既存トークンも検証可能
Day 60: [B] # A削除
ClientはJWKSを定期的にリフレッシュし、未知のkidに対応。
8. Client Credentials Flow
サーバー間通信用。ユーザーなし。
POST /token
Authorization: Basic <client_id:client_secret base64>
Content-Type: application/x-www-form-urlencoded
grant_type=client_credentials
&scope=read:users
ID Tokenなし。Secretの管理(Vault、Secrets Manager)、mTLS推奨、短命トークン。
9. Device Code Flow
TV、ゲーム機、CLIなどキーボード入力が困難なデバイス用。
POST /device/code
client_id=abc123
scope=openid profile
レスポンス:
{
"device_code": "SOME_LONG_CODE",
"user_code": "WDJB-MJHT",
"verification_uri": "https://example.com/device",
"expires_in": 1800,
"interval": 5
}
デバイスはURLとcodeを表示 → ユーザーがスマホでURLにアクセスしcodeを入力 → デバイスは/tokenをpolling。Google TV、GitHub CLI、gcloud auth loginで使用。
10. Refresh Token Flow
POST /token
grant_type=refresh_token
&refresh_token=OLD_REFRESH
&client_id=abc123
10.1 Rotation
OAuth 2.1推奨: refresh tokenを毎回交換。
- 盗まれたrefresh tokenが1回しか使えない。再使用をASが検知 → セッション全体無効化。
- 欠点: race condition(同時refresh要求)処理が必要。
10.2 保管
- Webアプリ: HTTPOnly、Secureクッキー。
- モバイル: OS keychain (iOS Keychain、Android Keystore)。
- SPA: 保管不可。PKCE + 短命access tokenのみ推奨。
11. Webアプリのセキュリティ
11.1 Redirect URI検証
ASは登録済みredirect_uriと完全一致するURLにのみリダイレクト。Wildcard禁止(OAuth 2.1)。
11.2 State
CSRF防御。ランダム値を認証要求に含め、レスポンスで一致確認。Stateなしだと攻撃者が自分のauthorization codeを被害者に注入できる。
11.3 Token Storage (SPA)
- LocalStorage: XSS脆弱。
- Memoryのみ: リロードで消失。
- HTTPOnlyクッキー: JSアクセス不可、CSRFは別途対策。
現実: 完璧な方法はない。ベストプラクティスは短命access token + メモリ保存、またはBackend-for-Frontendパターン(サーバーがトークン保管、ブラウザはセッションクッキーのみ)。
11.4 Implicit Flowが廃止された理由
トークンをURL fragmentで直接返す: ブラウザ履歴に残る、referrerで漏洩、JS経由でアクセス可能、refresh tokenなし。Authorization Code + PKCEが標準に。
11.5 Open Redirect
redirect_uriが厳密に検証されないと、ログイン後に悪意のあるサイトへリダイレクト → フィッシング。対策: 許可リストによる完全一致。
12. Token Binding: DPoPとmTLS
Bearer tokenの問題: トークンを盗めばどのclientでも使える。
12.1 mTLS Client Certificate
RFC 8705。ClientがTLS接続でclient certificateを使用。ASはトークンにcertificate thumbprintをバインド:
{ "cnf": { "x5t#S256": "SHA256(client_cert)" } }
RSは要求時にTLS接続のclient certを確認。
12.2 DPoP
RFC 9449。Clientが別の公開鍵ペアを生成し(セッションごと)、各要求に署名。
Step 1: Token requestにDPoPヘッダー追加。
Step 2: ASがトークンにDPoP thumbprintバインド:
{ "cnf": { "jkt": "SHA256(DPoP public key)" } }
Step 3: API呼び出し時に毎回DPoP JWS生成:
GET /api/users
Authorization: DPoP <access_token>
DPoP: <new JWS for this request>
RSは検証: (1) DPoP JWS署名、(2) cnf.jktと公開鍵一致、(3) htm/htu claimが実要求と一致。
12.3 Bearer vs DPoP
| 項目 | Bearer | DPoP |
|---|---|---|
| トークン盗難時 | 攻撃者が使用可能 | 使用不可 (private keyなし) |
| Client複雑度 | 低 | 高 (毎要求署名) |
| 採用率 | 非常に高い | 成長中 (金融、ヘルスケア) |
13. FAPI — Financial-grade API
金融機関のopen banking用プロファイル。
13.1 FAPI 1.0
- Baseline (読み取り): PKCE + state/nonce必須。
- Advanced (読み書き): PAR (Pushed Authorization Request)、JARM (JWT Response)、mTLSまたはDPoPによるtoken binding、署名されたRequest Object。
13.2 PAR
通常のOAuth:
GET /authorize?response_type=code&...&scope=...&state=...
パラメータがURLに露出、改ざんリスク。
PAR:
POST /par
<request object>
Response: { "request_uri": "urn:request:xyz" }
その後:
GET /authorize?client_id=...&request_uri=urn:request:xyz
URLが短く安全、署名されたrequest object。
13.3 FAPI 2.0
2024年以降進行中。よりシンプルで強力: PAR + DPoPデフォルト、sender-constrained tokens強制。英国、豪州、ブラジルのopen bankingで採用。
14. 実プロバイダー比較
- Auth0: 機能豊富、優れたドキュメント、高価、vendor lock-in。B2C/SaaS向け。
- Okta: エンタープライズ最強、SSO、ガバナンス。高価で複雑。
- Keycloak: オープンソース、セルフホスト可能、運用負担大。
- AWS Cognito: AWS統合、低価格、カスタマイズ制限。
- Google / Apple / GitHub: Social login、OIDC対応。
- Ory (Hydra/Kratos/Keto): クラウドネイティブなオープンソース代替。
15. よくあるセキュリティミス
Open Redirect — whitelistで解決。
Access Tokenで認証 — 誤り。OIDC ID TokenまたはUserInfo endpointを使う。
Refresh TokenをLocalStorageに — XSSで漏洩。HTTPOnlyクッキーまたはBFFパターン。
Scope過度要求 — 最小scopeのみ要求。
JWT検証省略 — 署名検証なしにpayloadを信頼すると誰でも偽造可能。jsonwebtoken、joseなどの検証ライブラリ使用。
exp検証漏れ — 必ずexp検証。
16. デバッグツール
- jwt.io — JWTデコーダー/検証器。本番トークンを貼らない。
- Postman / Insomnia — OAuth flowテスト。
- oidcdebugger.com — authorization request生成と検査。
- MITM Proxy / Charles — HTTPトラフィック傍受。
- OpenID Connect Conformance Test Suite — 公式準拠テスト。
17. 要約 — チートシート
┌─────────────────────────────────────────────────────┐
│ OAuth 2.0 / OIDC Cheat Sheet │
├─────────────────────────────────────────────────────┤
│ Roles: RO, Client, RS, AS │
│ │
│ Flows (2.1): │
│ 1. Authorization Code + PKCE (Web/Mobile/SPA) │
│ 2. Client Credentials (server-to-server) │
│ 3. Device Code (TV/CLI) │
│ 4. Refresh Token │
│ Deprecated: Implicit, Password │
│ │
│ Tokens: │
│ Access: API access, short-lived │
│ Refresh: renewal, long-lived, rotate │
│ ID: identity (JWT, OIDC only) │
│ │
│ JWT: header.payload.signature (Base64URL) │
│ alg=RS256/ES256/EdDSA (never none) │
│ │
│ PKCE: verifier → S256 challenge; S256 only │
│ OIDC Discovery: /.well-known/openid-configuration │
│ JWKS: kid-based key rotation │
│ │
│ Security: state, nonce, exact redirect_uri, HTTPS, │
│ short access tokens, PKCE always │
│ │
│ Token binding: Bearer (default) / mTLS / DPoP │
│ FAPI: PAR, JARM, mTLS or DPoP required │
└─────────────────────────────────────────────────────┘
18. クイズ
Q1. OAuth 2.0とOIDCの違いは?
A. OAuth 2.0は認可プロトコル、OIDCは認証プロトコル。OAuthは「このアプリが私のGmailを読むことを許可する」という委譲。OIDCは「このユーザーが今ログインした」という身元確認。技術的にはOIDCはOAuth 2.0の上にID Token (JWT)を追加したレイヤー。OAuthを認証に使うのはよくあるミスで、OAuthバグの90%はここから。
Q2. PKCEが解決する攻撃は?
A. Authorization Code Interception Attack。モバイルアプリやSPAはclient secretを安全に保管できず、authorization codeを傍受した悪意のあるアプリがtokenに交換可能だった。PKCEはclientがランダムなcode_verifierを作り、code_challenge = SHA256(verifier)をauthorization requestに送信、token request時に元のverifierを証明。途中でcodeを盗んでもverifierを知らなければtoken交換不可。OAuth 2.1ではすべてのclientで必須。
Q3. JWTのalg: none脆弱性とは?
A. JWT headerのalg値がnoneだと署名なし。初期のJWTライブラリがこれを受け入れ、攻撃者が{"alg":"none"}.{"admin":true}.のようなpayloadを作れば誰でも偽造可能だった。防御: 検証時に期待するアルゴリズムをハードコードし、algを信頼しない。関連脆弱性: HS/RS Key Confusion — サーバーがRS256公開鍵を持つ時、攻撃者がalg:HS256に変更し公開鍵をHMAC秘密として偽造。
Q4. OIDC DiscoveryとJWKSが解決する問題は?
A. 設定自動化と鍵ローテーション。Discovery (/.well-known/openid-configuration)はASのすべてのエンドポイント、アルゴリズム、scopeを1つのJSONで公開 → clientはissuer URLだけ知っていれば自動設定。JWKS (/.well-known/jwks.json)は署名公開鍵を配布し、各鍵にkidがあるためASが複数鍵を同時公開可能。ASが鍵を交換する時[A, B]状態を経て[B]に移行すれば既存トークンも検証可能。
Q5. Implicit Flowが廃止された理由は?
A. Access tokenがブラウザに露出するセキュリティ問題。Implicit flowはトークンをURL fragment (#access_token=...)で直接返す。問題: ブラウザ履歴に保存、refererヘッダーで漏洩、JavaScriptからアクセス可能、refresh tokenなし。Authorization Code + PKCEがはるかに安全でrefresh可能。OAuth 2.1で公式廃止。
Q6. DPoPは何を解決するか?
A. Bearer tokenの盗難リスク。通常のbearer tokenはトークン値を持つ誰でも使用可能。DPoP (Demonstrating Proof-of-Possession)はclientがセッションごとに鍵ペアを作り、毎API要求にDPoP JWSをprivate keyで署名して送信。ASはtoken発行時にDPoP公開鍵thumbprintをtokenにバインド (cnf.jkt)。RSは要求時に(1) tokenのjkt、(2) 現在のDPoP公開鍵hash、(3) 要求URL/methodを一致確認。盗まれたtokenもprivate keyなしでは使用不可。
Q7. Refresh token rotationとは何であり、なぜ必要か?
A. Refresh tokenを使用するたびに新しい値に交換し、以前の値を無効化すること。Refresh tokenは長期有効なので盗難リスクが大きい。Rotation ありなら攻撃者が一度使えば合法ユーザーのrefresh tokenが無効化、次のrefresh試行が失敗 → セッション乗っ取り検知。ASは「すでに使用されたrefresh tokenが再提出された」を検知したらトークンファミリー全体を無効化し、攻撃者と被害者両方のセッション終了。OAuth 2.1で強く推奨。