はじめに
最近twitterでOAuth2.1なるものの話を聞いたが、デファクトのOAuth2.0以外全然知らなかったので備忘録として簡単にまとめておく。結論から言うとOAuth 2.1 はOAuth 2.0 を捨てて作り直した新仕様ではなく、後付けのベストプラクティスを統合して整理した仕様。ちなみに執筆時点(2025-12)でInternet-Draftなので特に効力があるものでもない。
より具体には、次のものをまとめて整理した統合版である。
- OAuth 2.0 本体: RFC 6749 / RFC 6750
- PKCE: RFC 7636
- ネイティブアプリ向け: RFC 8252
- OAuth 2.0 Security Best Current Practice: RFC 9700(旧 draft-ietf-oauth-security-topics)
その過程で、実運用上危険・非推奨だったフローやパラメータの使い方が仕様レベルで削られている。この記事では、変更点にだけフォーカスして、フローで視覚化する。
OAuth 2.0 → 2.1 の主な変更点
OAuth 2.1 は、OAuth 2.0 と後付けベストプラクティスを統合して、危険な使い方を仕様レベルで潰した整理版である。
ここでは、変更点ごとに
- 何が変わるのか
- フロー(mermaid)
- なぜ危険だったのか(どう攻撃されるのか)
をまとめる。
1. Authorization Code + PKCE がデフォルトフローになる
何が変わるか
- Authorization Code フローでは PKCE 利用が前提になる
- 特に Public クライアント(SPA、ネイティブアプリ)では必須
- サーバサイド Web アプリでも PKCE 利用が推奨される
フロー(Authorization Code + PKCE)
なぜ PKCE なしが危険か
PKCE (ピクシーって呼び方可愛いよね)無しの Authorization Code フローでは、認可コードの奪取がそのままアクセストークン奪取につながる。
代表的なパターン:
- 攻撃者がブラウザやネットワークを制御して、
codeを横取り - 横取りした
codeを使って、自分の環境から/tokenを叩く - 正規クライアントになりすましてアクセストークンを取得
PKCE があると:
- 認可コード発行時に
code_challengeを AS へ登録 - トークン発行時に
code_verifierから計算した値と突き合わせ - クライアント自身しか知らない
code_verifierがないとトークンに交換できない
つまり、コードを盗られてもトークンにはできない状態になる。
2. redirect_uri は事前登録値と完全一致で検証
何が変わるか
- redirect_uri は AS に事前登録した値と文字列完全一致で検証する
- プレフィックス一致やワイルドカード(
https://example.com/*)は NG - 認可リクエストとトークンリクエストの両方で同じ URI を確認する
フロー(完全一致チェック)
なぜ緩いマッチが危険か(攻撃例)
もし AS が
- 「
https://app.example.com/で始まれば OK」 - 「
https://app.example.com/callback*を全部許可」
のようなチェックをしていると、攻撃者は:
- 攻撃者が管理する URL を
https://app.example.com/callback.evil.comのように作る - その URL を
redirect_uriとして指定してユーザを認可画面に誘導 - 認可完了後、AS が攻撃者サイトへ認可コードをリダイレクト
- 攻撃者がコードを使ってアクセストークンを取得
という形で、オープンリダイレクトを利用したトークン/コード窃取が成立する。ASがガバ実装してる時など
完全一致を強制することで、こうした紛らわしいURIを封じることが狙い。ログインページ前にいたところに戻す等の実装は/auth/callbackに戻ってきた後で、アプリは好きにリダイレクトしてよい。
3. Implicit Grant(response_type=token)削除
何が変わるか
-
response_type=tokenの Implicit フローは仕様から削除される - SPA でも Authorization Code + PKCE を使うことが前提になる
- アクセストークンを URL フラグメントで直接受け取る設計をやめる
フロー(旧 Implicit を拒否するイメージ)
なぜ Implicit が危険か(攻撃・脆弱性の観点)
Implicit では、アクセストークンがブラウザの URL フラグメント(#access_token=...)に露出する。この設計が現代ブラウザ環境では危険になっている。
代表的なリスク:
- フラグメントを JS でパースする実装が雑だと、別オリジンのスクリプトにトークンが漏れる
- ブラウザ拡張・プラグイン・ログ機構などが URL 全体を収集していると、そこでトークンが収集される
- ヒストリ、スクリーンショットなどで人間経由の漏洩も起こりやすい
また、Implicit には PKCE のようなトークン発行前の追加検証の仕組みがなく、
アクセストークンがその場で発行されるため、途中で何かがすり替わっていないかを確認する手段を持ちにくい。
Code + PKCE に統一することで:
- トークン発行は常にトークンエンドポイント経由
- コードと PKCE を使って盗難・リプレイを防ぐ
- ブラウザ URL にアクセストークンを出さない
という整理になる。
4. Resource Owner Password Credentials(ROPC)削除
何が変わるか
-
grant_type=passwordを使うフローは仕様から削除される - クライアントはユーザの ID / PW を直接扱わず、AS に認証を任せるのが前提になる
フロー(旧 ROPC を拒否するイメージ)
なぜ ROPC が危険か(攻撃・運用上の問題)
ROPC の最大の問題は何と言ってもクライアントがユーザの ID/PW を知ってしまう点である。
-
クライアントアプリが本当に信頼できるかどうかは環境次第
- モバイルアプリなら改ざん・リバースエンジニアリングし放題
- デスクトップアプリならマルウェアに覗かれても気づきにくい
-
ユーザは同じ ID/PW を他サービスでも使い回していることが多い
- 1 つのクライアントが漏洩すると、他サービスへの攻撃にも利用される
また、セキュリティ UX 的にも悪い:
- Phishing サイトが「正規クライアント」を装って ID/PW を集めやすい
- ユーザはどこにパスワードを入れてよくて、どこはダメなのか区別できなくなる
Authorization Code フロー + AS でのログイン画面に統一することで、
- パスワードを入れる場所は AS のログイン画面だけ
- クライアントはトークンだけを扱う
という境界が明確になり、設計・攻撃両面でマシになる。
5. Bearer トークンを URL クエリに載せるのを禁止
何が変わるか
-
https://api.example.com/resource?access_token=...のような URI クエリへのトークン埋め込みは禁止 -
Authorization: Bearer ...などヘッダやボディで送る
フロー(クエリを拒否してヘッダに統一)
なぜクエリが危険か(漏洩パス)
URI クエリは、システムのあちこちに残りやすい。
- Web サーバのアクセスログ
- リバースプロキシ、API Gateway のログ
- ブラウザのアドレスバー、履歴、オートコンプリート
- リファラヘッダ(別サイトにアクセスした際に送られることがある)
- APM / モニタリングツールが収集する URI
これら全てがトークンが雑にばら撒かれる経路になる。
ヘッダに載せる場合でもログに残りうるが、少なくともURI全文を集める仕組みに自動で吸い込まれるのは避けられる。
6. リフレッシュトークンはローテーション等で保護が前提
何が変わるか
-
リフレッシュトークンは、「そのまま長寿命トークンとして使い回す」前提が潰される
-
基本戦略は以下いずれか:
- リフレッシュトークンを 1 回使うたびに新しいものを発行し、古いものを失効(ローテーション)
- mTLS / DPoP などで「特定クライアントにだけ有効」な sender-constrained token にする
フロー(リフレッシュトークンのローテーション)
なぜ固定リフレッシュトークンが危険か(攻撃シナリオ)
固定の長寿命リフレッシュトークンが一度漏洩すると:
- 攻撃者は半永久的に新しいアクセストークンを取得し続けられる
- アクセストークンの短寿命化(例: 5 分、有効期限)にほとんど意味がなくなる
- トークンの利用ログから「どれが正規利用でどれが攻撃か」を見分けにくい
ローテーションを行うと:
- 1 回使うごとに旧トークンは無効化される
- 正規クライアントと攻撃者が同時にトークンを使うと、「2 回目以降」が必ずエラーになり、不正利用の兆候として検知できる
- 漏洩しても被害時間が限定される
sender-constrained(mTLS / DPoP)を組み合わせれば、「盗んだトークンを別の環境から使う」攻撃も防げる。
まとめ
OAuth 2.1 の変更点は、攻撃例を見ての通り、危ない運用パターンを仕様から追い出すことに集中している。今の時代で見るとそりゃマズいだろって実装だけど、OAuth 2.0ってもう15年ぐらい前の奴だし多少はね?
実装側は、ここで挙げたフローと攻撃パターンを踏まえて、自分の環境がどこまで 2.1 的な前提に寄っているかを確認すればよいかと思う。
これよりさらに上位版のFAPI2.0 (Financial Grade API)というものもあるので改めてまとめたい。