- Authors

- Name
- Youngju Kim
- @fjvbn20031
- はじめに
- 1. 認証と認可は別の問題である
- 2. OAuth 2.0 は認可フレームワークである — OIDC がその上に載せるもの
- 3. 推奨から外れたフローと、その代わりに来たもの
- 4. PKCE — 何を防ぎ、何を防がないのか
- 5. 三つのトークンを区別する
- 6. JWT: デコードは検証ではない
- 7. トークンをどこに置くか — そしてセッションをどう運用するか
- 8. 認可はどこで決まるのか
- 9. 自前で作るか — この決定の代価
- クイズ: 理解度を確認しましょう
- おわりに
- 参考資料
- 関連記事
はじめに
このブログにはすでに OAuth 2.0完全攻略 と JWTとセッション、何をいつ、そしてフレームワーク別の SSO シリーズがあります。それらが説明するのはプロトコルがどう動くかです。この記事は角度が違います。現場で繰り返される誤解が正確にどの地点で生まれるのかを、仕様の原文で正すことが目的です。
始める前に一つはっきりさせておきます。この記事はレシピではありません。 セキュリティの助言は、前提が付いて初めて意味を持ちます。「Cookie に入れましょう」「JWT を使いましょう」といった文は、前提を省いた瞬間に誤った助言になります。ですから本記事では、推奨ごとにそれが成り立つ条件を併記します。条件が違えば、結論も変わらなければなりません。
1. 認証と認可は別の問題である
- 認証(Authentication): リクエストを送った主体が誰かを確認すること
- 認可(Authorization): 確認された主体がこの操作をしてよいかを判断すること
この区別は説明のための修辞ではありません。HTTP 自体が二つの状態を別のコードに分けています。RFC 9110 §15.5.2 は 401 を「対象リソースに対する有効な認証資格情報を欠くため、リクエストが適用されなかった」と定義し、§15.5.4 は 403 を「サーバーはリクエストを理解したが履行を拒否する」と定義します。401 は認証の問題、403 は認可の問題です。
401 の名前が Unauthorized であることは歴史的な事故に近いものです。名前は認可に見えますが、意味は認証です。
実務でこの区別が効くのは、クライアントの挙動が変わるからです。
- 未ログインの利用者に 403 を返すと、クライアントは再ログインの流れを始める根拠を失います。
- 権限のない利用者に 401 を返すと、クライアントは再ログインしてまた 401 を受け取り、無限ループに陥ります。
ただし意図的な例外があります。リソースの存在そのものを隠す必要がある場合、403 ではなく 404 を返す選択があります。他人の文書 ID を入れて 403 が返れば「その ID は存在する」という情報が漏れるからです。これは正確な意味の伝達と情報漏えいの最小化のトレードオフであり、どちらが正しいと一般化はできません。決めて、文書に書けばよいのです。
2. OAuth 2.0 は認可フレームワークである — OIDC がその上に載せるもの
最も多く繰り返される誤解です。 OAuth 2.0 をログインのプロトコルとして理解したまま設計を始めると、その後のすべての決定がずれます。
RFC 6749 の題名は The OAuth 2.0 Authorization Framework です。文書は自らを認可フレームワークと規定しており、認証プロトコルだとは述べていません。定義する四つの役割も、アクセス委譲の言葉で書かれています。
| 役割 | RFC 6749 §1.1 の定義 |
|---|---|
| リソースオーナー | 保護リソースへのアクセスを許可できる主体。人であればエンドユーザー |
| クライアント | リソースオーナーに代わって保護リソースを要求するアプリケーション |
| 認可サーバー | オーナーを認証し認可を得たうえでアクセストークンを発行するサーバー |
| リソースサーバー | 保護リソースをホストし、アクセストークンでリクエストを受けるサーバー |
ここでアクセストークンは、「このクライアントがこのリソースにこれだけアクセスしてよい」という証書です。「この人が誰か」という証明ではありません。二つの文は違います。
ですからアクセストークンをそのままログインの証拠として使うパターンは危険です。アクセストークンには「このトークンが自分(クライアント)に対して発行されたものか」をクライアントが確認する標準的な仕組みがありません。別のアプリのために発行されたトークンを自分のサーバーに提出されても、区別できない状況が起こり得ます。
OIDC はまさにこの空白を埋めます。 OpenID Connect Core 1.0 のアブストラクト冒頭はこうです。「OpenID Connect 1.0 は OAuth 2.0 プロトコルの上の単純なアイデンティティレイヤーである。」§2 は ID トークンを「認可サーバーが実施したエンドユーザー認証に関するクレームを含むセキュリティトークン」と定義し、形式は JWT です。
ID トークンの必須クレームは iss、sub、aud、exp、iat で、aud はそのクライアントの client_id を必ず含みます。この aud の検査こそ、アクセストークンにはない仕組みです。自分の client_id が入っていない ID トークンは自分宛てではないので拒否します。
まとめるとこうなります。
- OAuth 2.0 だけ → アクセス委譲はできるが、認証プロトコルとしての保証はない
- OIDC を使う → ID トークンという検証可能な認証結果が得られる
- 「ソーシャルログイン」と呼ばれるものの多くは、実は OIDC か、OAuth の上に各事業者が載せた非標準の取り決め
3. 推奨から外れたフローと、その代わりに来たもの
OAuth 2.0 のセキュリティ指針は 2012 年の RFC 6749 に留まっていません。RFC 9700(2025年1月、BCP 240) が現行のセキュリティベストプラクティスであり、RFC 6749・6750・6819 を更新します。ここで二つのグラントの位置づけが変わりました。
3-1. インプリシットグラント — SHOULD NOT
RFC 9700 §2.1.2 はこう記します。「クライアントはインプリシットグラント(レスポンスタイプ token)や、認可レスポンスでアクセストークンを発行する他のレスポンスタイプを使用すべきではない(SHOULD NOT)。ただし認可レスポンスへのアクセストークン注入が防止される場合を除く。」
理由は二つです。アクセストークンが認可レスポンス(フラグメント、リダイレクト、ブラウザ履歴、リファラ)を流れる過程で漏えいとリプレイに晒されること、そして認可レスポンスで発行されたトークンを特定のクライアントに束縛する標準化された送信者制約の方法が存在しないことです。
3-2. リソースオーナーパスワードクレデンシャル(ROPC) — MUST NOT
§2.4 の表現はより強いものです。「リソースオーナーパスワードクレデンシャルグラントは使用してはならない(MUST NOT)。」
理由は、資格情報をそのままクライアントに晒して攻撃面を広げること、そして二要素認証や複数段階の利用者操作を要する認証手順とそもそも噛み合わないことです。
SHOULD NOT と MUST NOT の違いを読み飛ばさないでください。 インプリシットは条件付きの禁止、ROPC は無条件の禁止です。文書に根拠を書くとき、この差が実際に効きます。
3-3. 代わりに来たもの
現行の推奨は認可コードグラント + PKCE です。RFC 9700 §2.1.1 は三つの文で釘を刺します。
- 「パブリッククライアントは PKCE を使用しなければならない(MUST)。」
- 「コンフィデンシャルクライアントの場合、PKCE の使用が推奨される(RECOMMENDED)。」
- 「認可サーバーは PKCE をサポートしなければならない(MUST)。」
同じ節はリダイレクト URI についても要求します。認可サーバーは事前登録された URI と完全一致の文字列比較で照合しなければならず、ネイティブアプリの localhost リダイレクトにおけるポート番号だけが例外です。部分一致や前方一致を許す実装はこの要求に違反しています。
リフレッシュトークンも同様です。§2.2.2 は、パブリッククライアントのリフレッシュトークンが送信者制約(sender-constrained)されるか、ローテーションされなければならないと要求します(MUST)。
4. PKCE — 何を防ぎ、何を防がないのか
RFC 7636 が定義する PKCE が防ぐのは認可コード横取り攻撃(authorization code interception attack) です。クライアントとサーバー間の TLS 区間ではなく、端末内のアプリ間通信のような保護されていない経路で認可コードが奪われ、攻撃者がそれをトークンに交換するシナリオです。
動作は単純です。
クライアント 認可サーバー
| |
|-- code_verifier 生成(ランダム32オクテット) |
|-- code_challenge = BASE64URL(SHA256(verifier))|
| |
|--- 認可リクエスト + code_challenge + S256 --->|
| | (challenge を保存)
|<-- 認可コード -------------------------------|
| |
|--- トークンリクエスト + code + verifier ----->|
| | SHA256(verifier) == challenge ?
|<-- アクセストークン --------------------------|
code_verifier は 43〜128 文字で、仕様は 32 オクテットの乱数を生成して base64url でエンコードすることを推奨します。code_challenge_method は plain と S256 の二つで、S256 がサーバー実装必須(MTI)であり、S256 を使えるクライアントは S256 を使わなければなりません。plain は制約のあるレガシー環境のために残っているだけです。
PKCE が防がないものも明確にすべきです。
- PKCE は CSRF 対策ではありません。
stateパラメータ(または OIDC のnonce)は依然として必要です。 - PKCE は、すでに発行されたアクセストークンが漏えいした後を守りません。
- 認可サーバーが PKCE ダウングレードを防がなければ無力化されます。RFC 9700 §2.1.1 は「認可リクエストに
code_challengeがなかったなら、code_verifierを含むトークンリクエストを受け付けないこと」を MUST で要求します。クライアント側だけ PKCE を付けても終わりではありません。
5. 三つのトークンを区別する
名前がすべて「トークン」なので一括りにされがちですが、消費者も寿命も異なります。
| トークン | 誰が検証するか | どこへ送るか | クライアントが中身を読んでよいか |
|---|---|---|---|
| アクセストークン | リソースサーバー | リソースサーバー(API) | いいえ。不透明でも正常 |
| リフレッシュトークン | 認可サーバー | 認可サーバーのトークン端点 | いいえ |
| ID トークン | クライアント | どこにも送らない | はい。そのためのトークン |
ここから最も多い二つの誤りが生じます。
第一に、ID トークンを API 呼び出しの Bearer トークンとして使うこと。 ID トークンはクライアントが「利用者が認証された」事実を確認するためのものです。リソースサーバーに提出するために作られたものではなく、aud が API ではなくクライアントを指すため、リソースサーバーから見れば対象の食い違ったトークンです。
第二に、アクセストークンをパースして利用者情報を取り出すこと。 アクセストークンが JWT である保証はありません。不透明な文字列でも仕様上は正常であり、事業者が形式を変えてもクライアントに抗議する根拠はありません。利用者情報が必要なら、ID トークンのクレームか OIDC Core §5.3 の UserInfo エンドポイントを使うのが契約に沿っています。
6. JWT: デコードは検証ではない
RFC 7519 が定義する JWT は JWS(署名または MAC で保護) か JWE(暗号化) です。ここから出発すると誤解が整理されます。
6-1. base64url はエンコードであって保護ではない
JWT のペイロードは誰でも読めます。暗号化ではなくエンコードだからです。JWT のペイロードに機微情報を入れることは、公開したのと同じです。 個人識別番号、内部システムの識別子、認可ポリシーの詳細などを入れないでください。
6-2. デコーダが中身を見せたからといって、そのトークンが有効なわけではない
レビューや障害対応の最中に実際に起きることです。デコーダに貼り付けて sub と exp がもっともらしいから通過、と判断してしまう。デコードは署名検証と何の関係もありません。 このブログの JWT デコーダ も、署名を検証しないと明記しています。デコーダは中身を読む道具であって、真正性を判定する道具ではありません。
6-3. alg: none
RFC 7519 §6 は、alg が none で署名が空文字列の非保護 JWT を定義します。仕様がこれを許すのはセキュリティが外部の手段で提供される場合に限られます。問題は、検証側がこの条件を忘れてトークンの言うとおりに従うときです。攻撃者が署名を消して alg を none に変えて送れば、そのサーバーでは認証が消滅します。
6-4. アルゴリズム混同(algorithm confusion)
より巧妙なほうです。サーバーが RS256(公開鍵署名)で発行しているのに、検証関数がトークンの alg ヘッダーを読んでそのまま使うとします。攻撃者が alg を HS256 に変え、サーバーの公開鍵を HMAC の秘密鍵として署名すると、検証コードは同じ公開鍵で HMAC を計算して一致と判定します。公開鍵は公開された値なので、誰でもトークンを偽造できるようになります。
RFC 8725(BCP 225)はこの問題を正面から扱います。
- §3.1「ライブラリは呼び出し側がサポートするアルゴリズムの集合を指定できるようにしなければならず(MUST)、暗号操作の実行時にそれ以外のアルゴリズムを使用してはならない(MUST NOT)。」
- §3.1「ライブラリは
algまたはencヘッダーが、実際の暗号操作に使われるアルゴリズムと同じものを指定していることを保証しなければならない(MUST)。」 - §3.1「各鍵は正確に一つのアルゴリズムとともに使用されなければならず(MUST)、これは暗号操作の実行時に確認されなければならない(MUST)。」
一文に縮めるとこうです。トークンが自ら申告したアルゴリズムを信用しないでください。 検証アルゴリズムはアプリケーションがトークンの外側で固定すべきものです。
6-5. 何を検査するかは仕様が決めてくれない
RFC 7519 において iss、sub、aud、exp、nbf、iat、jti はすべて OPTIONAL です。この事実はしばしば逆に読まれます。任意であるとは「検査しなくてよい」ではなく、何を必ず検査するかは仕様ではなくアプリケーションが決めなければならないという意味です。「ライブラリが検証してくれるだろう」という期待が外れるのがここです。既定で有効期限だけを見て発行者と対象を見ない設定は珍しくありません。
最低限この程度はアプリケーションが明示的に決め、コードに残すべきです。
[ ] 署名検証に使うアルゴリズムをコードで固定したか(トークンのヘッダーを読まずに)
[ ] 検証鍵の出所が固定されているか(JWKS URL、鍵ローテーション対応を含む)
[ ] iss が期待する発行者か
[ ] aud が自分を指しているか
[ ] exp / nbf を検査するか、許容する時刻ずれは何秒か
[ ] 必要な場合 jti で再利用を防ぐか
6-6. 取り消しは依然として難しい
署名が有効で期限内なら、そのトークンは有効です。それがステートレス検証の利点であり代価でもあります。ログアウト、権限剥奪、アカウント停止を即座に反映するには、結局サーバー側の状態(ブロックリスト、トークンバージョン、セッション照会)が必要になり、その瞬間に「ステートレス」という利点の相当部分は失われます。
ここに正解はありません。軸はアクセストークンの寿命です。短くすれば取り消しの遅延は減りますが、認可サーバーのトラフィックと障害の結合度が上がります。長くすれば逆です。この選択は、組織が耐えられる「権限剥奪の遅延」を何分と見るかの問題であり、その数字を決めて書き残すことが設計です。
7. トークンをどこに置くか — そしてセッションをどう運用するか
ここで「正解は一つ」と言う記事には注意してください。 双方に費用があり、費用の種類が違うだけです。
7-1. Web Storage(localStorage / sessionStorage)
- 弱点: 同一オリジンで実行されるすべての JavaScript が読めます。OWASP セッション管理チートシートは「認証トークン、セッション ID、JWT、リフレッシュトークン、その他いかなる資格情報も
localStorageやsessionStorageに保存しないこと」を明記し、理由として「たった一つの XSS 脆弱性がすべてのトークンを露出させる」を挙げます。 - 強み: ブラウザが自動で付与しないため、CSRF の面は構造的に小さくなります。クロスオリジン API 呼び出しにヘッダーで載せるのも単純です。
7-2. Cookie(HttpOnly、Secure、SameSite)
- 強み:
HttpOnlyなら JavaScript が値を読めません。Secureは OWASP の表現で「中間者攻撃によるセッション ID の露出を防ぐために必須」です。 - 弱点: ブラウザがリクエストに自動で付けるため、CSRF の面が生じます。OWASP もこの点を併記しています。「XSS 攻撃が CSRF 攻撃と結合すると、Web アプリケーションへ送られるリクエストにはセッション Cookie が含まれる。ブラウザはリクエスト送信時に常に Cookie を含めるからである。」したがってセッション Cookie は
SameSite=Strict(推奨)またはSameSite=Laxを明示すべきで、SameSite=NoneをSecureなしで使ってはなりません。
7-3. このトレードオフを正直にまとめると
Cookie はトークンの窃取を難しくし、リクエスト偽造を容易にします。Web Storage はその逆です。 そして決定的に、XSS が存在すればどちらも安全ではありません。 Cookie を読めなくても、攻撃者はそのブラウザから認証済みのリクエストを送れるからです。保存場所の選択は XSS 対策の代わりにはなりません。
OWASP のように Cookie を勧める文書が多数であり、その根拠も明確です。ただしその推奨には前提が付きます。
- フロントエンドと API が Cookie を共有できるサイト構成か
- CSRF 対策(SameSite、CSRF トークン、Origin/Referer 検証)を併せて実装するか
- サードパーティコンテキストやネイティブアプリのように、Cookie が噛み合わない配置ではないか
前提が違えば結論も変わるべきです。前提を書かないセキュリティ助言は、次に読む人にとって危険です。
7-4. セッションを運用するということ
トークンであれセッション ID であれ、運用の規則は似ています。OWASP セッション管理チートシートに沿えば、最低限これだけは必要です。
- エントロピー: セッション識別子は最低 64 ビットのエントロピー(16 進表記で 16 文字以上)を持つべきです。値の一部が固定または予測可能なら、実効エントロピーはその分減ります。
- アイドルタイムアウトと絶対タイムアウト: 活動がないときに失効させるものと、活動と無関係に生成時点から最大寿命を強制するものは別の仕組みです。両方必要です。文書が示す感覚は、価値の高いアプリケーションで 2〜5 分、リスクの低いもので 15〜30 分です。
- セッション固定への対策: 「セッション ID は、関連する利用者セッション内で権限レベルが変更された後、必ず更新または再生成されなければならない。」ログイン成功の直後が代表的な時点です。
8. 認可はどこで決まるのか
モデルの話から始めると議論が上滑りします。順番を変えましょう。
8-1. モデルは三つ程度で足りる
- RBAC: ロールに権限を束ねます。ロール数が管理可能なときに噛み合います。
- ABAC: 属性(部署、地域、時刻、等級)で判断します。ロールの爆発は避けられますが、規則の検証とデバッグが難しくなります。
- ReBAC: 「この文書の所有者と同じチームか」のように関係で判断します。共有・協業モデルに合い、グラフ照会の費用を払います。
三つは排他的ではありません。多くは RBAC で大枠を作り、特定の地点で属性や関係を混ぜます。
8-2. 本当の問題は決定地点である
認可の失敗の相当数は、モデルの選択を誤ったからではなく、判定が起きる場所を誤ったために生じます。
クライアント → [APIゲートウェイ] → [サービス] → [データアクセス層] → DB
| | |
認証+早期遮断 業務規則の判定 所有権の判定
- ゲートウェイだけで判定すると、ゲートウェイを経由しない内部呼び出し・バッチ・管理ツールがそのまま迂回します。
- サービスごとに判定すると、同じ規則が複数箇所に散らばり、片方だけ直る事故が起きます。
実務的な規則はこうです。認可判定はデータに最も近い場所で最低一度は必ず起きなければなりません。 前段の判定は早期遮断(性能、利用者体験、ログのノイズ削減)のためのものであり、信頼の根拠ではありません。
8-3. 最も頻繁に抜ける検査
オブジェクトレベルの認可です。認証は通り、ロールも合っているのに、「この利用者がこの特定のリソースへの権限を持つか」を誰も確認していない場合です。パスの識別子を差し替えるだけで他人のデータを読む事故はここから生まれます。一覧照会は所有者条件で絞り込みながら、単件照会で同じ条件を落とすパターンが特に多く見られます。
そして判定は閉じる方向に失敗しなければなりません。権限照会がタイムアウトしたときの既定値が許可なら、認可システムの障害がそのまま全面的な権限開放になります。
拒否理由をどこまで詳しく伝えるかも決定事項です。詳しいほどデバッグは楽になり、情報はより漏れます。内部の管理ツールと公開 API が同じ方針を使う理由はありません。
9. 自前で作るか — この決定の代価
最後に、最もよく言葉を濁される問いです。
認証を自前で実装することは禁じられた行為ではありません。ただし費用の大きな決定であり、その費用は最初のリリースではなくその後に請求されます。 この文はそのまま議事録に書いておくほうがよいものです。
9-1. 自前で作れば一緒に引き受けるもの
ログイン画面とパスワード照合は、この一覧の最初の項目にすぎません。
- パスワードの保存(適切なハッシュ関数とパラメータ、そのパラメータの定期的な更新)
- アカウント復旧の流れ — 統計的には認証そのものよりここのほうが多く破られます
- 利用者列挙の防止: OWASP は応答を「一般的な方法で」行うことを求め、
Login failed; Invalid user ID or passwordのような単一のメッセージを例に挙げます。HTTP ステータスコードまで同一でなければなりません。 文書は「HTTP 応答コードが異なるとアカウントの有効性が漏れうる」と指摘します - ロックアウト方針: 失敗カウンタは「送信元 IP ではなくアカウント自体に」結び付けるべきで、1 秒から始めて失敗ごとに倍増する指数的ロックアウトが推奨されます。そしてロックアウト中もパスワード復旧の経路は開いていなければなりません。 さもなければロックアウト自体がサービス妨害の手段になります
- MFA: OWASP は「多要素認証はパスワード関連攻撃の大多数に対する断然最良の防御である」と記します
- セッションの無効化、同時セッション方針、デバイス管理、監査ログ、侵害時の一斉ログアウト
9-2. 委譲すれば費用が消えるのではなく、種類が変わる
- 事業者の障害がそのままログイン障害になります。この結合を SLO に反映する必要があります
- アカウント移行と事業者の乗り換えが難しくなります。利用者識別子を事業者の
subに直結すると、移行時点で同一性が切れます - 利用者数課金、データの保存場所、監査要件が新たに入ってきます
9-3. 判断に実際に使われる軸
- 侵害時の被害規模(決済・医療・未成年のデータがあるか)
- 規制が特定の方式を要求するか
- この領域を継続的に面倒を見る人がチームにいるか — 構築ではなく維持が基準です
- 求められる認証方式の幅(企業 SSO、SAML、パスキー、ソーシャル)
どちらを選ぶにせよ、選んだ理由と前提を文書に残してください。 2 年後にこの決定を振り返る人に必要なのは結論ではなく、その時点の前提です。それが本シリーズ第 1 回で扱った設計文書の仕事です。
クイズ: 理解度を確認しましょう
クイズ1: モバイルアプリのレビューで、認可リクエストに response_type=token を使いリダイレクトのフラグメントでアクセストークンを受け取るコードを見つけました。何が問題で、何に変えるべきですか。
正解: インプリシットグラントです。RFC 9700 §2.1.2 が SHOULD NOT と規定しており、認可コードグラント + PKCE に変えるべきです。モバイルアプリはパブリッククライアントなので PKCE は MUST です。
解説: 問題は「古いから」ではありません。アクセストークンが認可レスポンスを流れる過程でブラウザ履歴・リファラ・ログから漏れうること、そして認可レスポンスで発行されたトークンを特定のクライアントに束縛する標準的な送信者制約の方法がないことです。移行の際、クライアント側に PKCE を付けて終わりにしてはいけません。認可サーバーが PKCE ダウングレードを防ぐか、つまり code_challenge なしで始まったリクエストに code_verifier を含むトークンリクエストを拒否するかも確認が必要です。
クイズ2: 障害対応中に同僚が「トークンをデコーダに入れたら sub も合っているし exp も切れていないので、このリクエストは正常だ」と言います。何が間違っていますか。
正解: デコードは検証ではありません。署名を確認していないので、そのトークンを自分たちのサーバーが発行したのかどうか分かりません。
解説: JWT のペイロードは base64url エンコードにすぎないため、誰でも好きな値で作れます。このブログの JWT デコーダを含め、多くのデコーダは署名を検証しないと明記しています。判断に必要なのはペイロードのもっともらしさではなく、署名検証を通過したか、そして iss と aud が期待値と一致するかです。付け加えると、本番のトークンをデコーダに貼り付ける行為自体が、資格情報を外部ツールに露出させることです。
クイズ3: サーバーは RS256 で JWT を発行します。検証コードはトークンの alg ヘッダーを読み、そのアルゴリズムで検証します。どんな攻撃が可能ですか。
正解: アルゴリズム混同攻撃です。攻撃者が alg を HS256 に変え、サーバーの公開鍵を HMAC の秘密鍵として署名すると、検証コードは同じ公開鍵で HMAC を計算して一致と判定します。
解説: 公開鍵は文字どおり公開された値なので、誰でも有効なトークンを偽造できるようになります。RFC 8725 §3.1 は、ライブラリが呼び出し側にサポートするアルゴリズムの集合を指定させなければならず(MUST)、それ以外を使ってはならず(MUST NOT)、各鍵は正確に一つのアルゴリズムとともに使用されなければならない(MUST)と規定します。原則は一つです。検証アルゴリズムはトークンではなくアプリケーションが決めます。同じ理由で alg: none を受け入れる設定も併せて点検すべきです。
クイズ4: チームがトークンを localStorage から HttpOnly Cookie に移しました。「これで XSS は解決した」がなぜ間違いですか。
正解: 二つあります。第一に、HttpOnly はトークンの値を読むことを防ぐだけで、XSS そのものを防ぎません。攻撃者はそのブラウザから認証済みのリクエストをそのまま送れます。第二に、Cookie に移したことで CSRF の面が新たに生じました。
解説: 保存場所の変更はリスクの種類を変えるのであって、なくすのではありません。Cookie は窃取を難しくする代わりに、ブラウザが自動で付けるためリクエスト偽造の経路を開きます。OWASP も XSS と CSRF が結合するとセッション Cookie がリクエストに含まれる点を併記しています。移行には SameSite の設定、CSRF 対策、そして何より出力エンコーディングや CSP といった XSS 対策が伴わなければなりません。
クイズ5: API ゲートウェイが JWT の署名と有効期限を検証し、通過したリクエストだけを注文サービスへ渡します。注文照会 API に他人の注文 ID を入れるとデータがそのまま返ります。何が抜けていますか。
正解: オブジェクトレベルの認可です。ゲートウェイは「この要求者が認証済みの利用者か」だけを確認し、「この利用者がこの特定の注文の所有者か」は誰も確認していません。
解説: 認証と認可を同じ地点で終えたと錯覚したときに出る典型的な事故です。ゲートウェイの判定は早期遮断のためのものであり、信頼の根拠ではありません。所有権の判定はデータに最も近い場所、つまり注文を実際に照会する層で行われるべきです。一覧照会は所有者条件で絞りながら単件照会で落とすパターンが特に多いため、単件照会の経路を個別に点検するとよいでしょう。
クイズ6: フロントエンドが OIDC ログイン後に受け取った ID トークンを、そのまま API 呼び出しの Bearer トークンとして送っています。なぜ問題ですか。
正解: ID トークンはクライアントが認証の事実を確認するためのトークンであり、リソースサーバーに提出するトークンではありません。aud が API ではなくクライアントの client_id を指すため、リソースサーバーから見れば対象の食い違ったトークンです。
解説: リソースアクセスにはアクセストークンを使うべきです。リソースサーバーがこの状況をそのまま受け入れているなら、そのサーバーは aud の検査をしていないという意味でもあります。これは他のクライアントのために発行されたトークンまで受け入れるという、より広い問題につながります。逆方向の誤りも併せて見てください。アクセストークンをパースして利用者情報を取り出すことも契約違反です。アクセストークンは JWT である義務を負いません。
おわりに
この記事で繰り返した文をもう一度集めるとこうなります。
- OAuth 2.0 は認可フレームワークであり、認証は OIDC が ID トークンで埋めます
- インプリシットは SHOULD NOT、ROPC は MUST NOT であり、代替は認可コード + PKCE です
- デコードは検証ではなく、トークンが申告したアルゴリズムは根拠になりません
- 保存場所の選択はリスクをなくさず、種類を変えます。XSS があればどちらも安全ではありません
- 認可判定はデータに最も近い場所で最低一度は必ず起きなければなりません
最後に最も大切なこと。前提のないセキュリティ助言を書くことも、受け取ることもしないでください。 「こうすれば安全です」という文には、ほぼ常に「こういう条件では」が前に省略されています。省略された条件が自分たちの環境で成り立たないとき、その文が残すのは安全だという錯覚だけです。条件を書く習慣ひとつが、この領域で最も安価で最も効果的な防御です。
参考資料
- RFC 6749 — The OAuth 2.0 Authorization Framework — 四つの役割の定義(§1.1)、四つのグラント(§1.3)、コンフィデンシャル/パブリッククライアントの区別(§2.1)、そして文書が自らを認可フレームワークと規定している点。2026-08-15 確認。
- RFC 9700 — Best Current Practice for OAuth 2.0 Security — インプリシット SHOULD NOT(§2.1.2)、ROPC MUST NOT(§2.4)、PKCE 要件とダウングレード防止(§2.1.1)、リダイレクト URI 完全一致(§4.1.3)、リフレッシュトークンのローテーション・送信者制約(§2.2.2)。2025年1月、BCP 240。2026-08-15 確認。
- RFC 7636 — Proof Key for Code Exchange by OAuth Public Clients — 認可コード横取り攻撃、code_verifier の長さ(43〜128文字)と生成の推奨、S256 と plain。2026-08-15 確認。
- RFC 7519 — JSON Web Token — JWS/JWE の区別、非保護 JWT と
alg: noneの条件(§6)、登録クレームがすべて OPTIONAL である事実。2026-08-15 確認。 - RFC 8725 — JSON Web Token Best Current Practices — アルゴリズム混同、サポートアルゴリズム集合の指定要求、
algヘッダーと実際の演算の一致要求、鍵ごとに単一アルゴリズムの要求(§3.1)。BCP 225。2026-08-15 確認。 - OpenID Connect Core 1.0 — 「OAuth 2.0 の上の単純なアイデンティティレイヤー」というアブストラクトの文、ID トークンの定義(§2)と必須クレーム、UserInfo エンドポイント(§5.3)。2026-08-15 確認。
- RFC 9110 — HTTP Semantics — 401 と 403 の定義および認証/認可の区別(§15.5.2、§15.5.4)。2026-08-15 確認。
- OWASP Authentication Cheat Sheet — 一般化した失敗応答と HTTP ステータスコードの一致、アカウント基準の失敗カウンタと指数的ロックアウト、ロックアウト中の復旧経路の維持、MFA の推奨。2026-08-15 確認。
- OWASP Session Management Cheat Sheet — Web Storage への保存禁止とその根拠、Cookie 属性ごとの防御範囲と XSS・CSRF の結合、64 ビットのエントロピー、アイドル/絶対タイムアウト、権限変更後のセッション再生成。2026-08-15 確認。
- 認可の決定地点(ゲートウェイ・サービス・データ層)の配置原則と、自前実装の可否を判断する軸は、上記の文書にそのまま載っている項目ではなく、それらの要件をもとに本記事で整理したものです。
関連記事
- このブログの関連記事: OAuth 2.0完全攻略 — 認証と認可のすべて
- このブログの関連記事: JWTとセッション、何をいつ — 状態をどこに置くかで整理する認証の選択
- 関連ツール: JWT デコーダ — トークンの中身を読む道具です。署名を検証しないため、真正性の判定には使えません
- 関連ツール: 認証フロー可視化、認証セキュリティラボ、HTTP ステータスコード
完全ガイドシリーズ
- 前の記事: API設計 完全ガイド: 元に戻せない決定から決める
- 次の記事: エラー処理 完全ガイド: 失敗を契約として設計する