OAuth 2.0 / 2.1 を安全に実装・運用するための要点をまとめたドキュメント。 主な根拠は RFC 9700(OAuth 2.0 Security Best Current Practice) と OAuth 2.1 ドラフト(draft-ietf-oauth-v2-1)。
OAuth 2.1 はまだ IETF の Internet-Draft(RFC 未確定)だが、中身は OAuth 2.0 + PKCE + Security BCP の統合であり、主要な IdP はすでにその推奨に沿っている。新規実装は 2.1 準拠を前提にしてよい。
目次
- 1. フロー(グラントタイプ)の選択
- 2. PKCE(Proof Key for Code Exchange)
- 3. リダイレクト URI
- 4. CSRF・ミックスアップ対策
- 5. アクセストークン
- 6. リフレッシュトークン
- 7. クライアント認証(コンフィデンシャルクライアント)
- 8. クライアント種別ごとの指針
- 9. 認可サーバー側の設定
- 10. 認証と認可を混同しない
- 11. 運用・監視
- チェックリスト
1. フロー(グラントタイプ)の選択
| ユースケース | 使うべきフロー | 備考 |
|---|---|---|
| ユーザーが関与する Web アプリ / SPA / モバイル / デスクトップ | Authorization Code + PKCE | 全クライアントで PKCE 必須 |
| サーバー間通信(M2M) | Client Credentials | ユーザー文脈なし |
| 入力制約デバイス(TV、CLI、IoT) | Device Authorization Grant(RFC 8628) | |
| トークン更新 | Refresh Token | ローテーション or 送信者制約を付ける |
使ってはいけないフロー
- Implicit Grant:アクセストークンが URL フラグメントに露出する。OAuth 2.1 で削除。
- Resource Owner Password Credentials (ROPC):クライアントがパスワードを直接扱う。MFA・フィッシング対策と両立しない。OAuth 2.1 で削除。
2. PKCE(Proof Key for Code Exchange)
- パブリック/コンフィデンシャルを問わず全クライアントで使う。 認可コード横取り・コードインジェクション対策になる。
code_challenge_methodはS256のみ。plainは使わない。code_verifierは暗号論的に安全な乱数で 43〜128 文字。リクエストごとに新規生成し、使い終わったら破棄。- 認可サーバー側は、PKCE を伴わないリクエストを拒否する設定にする。
3. リダイレクト URI
- 事前登録した URI との完全一致(exact string match)で検証する。 ワイルドカードや前方一致は使わない(オープンリダイレクト経由のコード漏えいの原因)。
- 例外:ネイティブアプリのループバック(
http://127.0.0.1:{port})はポート番号のみ可変を許容。
- 例外:ネイティブアプリのループバック(
- 本番は HTTPS 必須。
- リダイレクト先ページにオープンリダイレクトを作らない。
- 認可レスポンスを受けたページでは、外部リソース読み込みや
Refererでコードが漏れないようReferrer-Policy: no-referrer等を設定する。
4. CSRF・ミックスアップ対策
state:推測不能な値をセッションに紐付けて送り、戻りで照合する。PKCE を使っていても、アプリ側の CSRF 対策として併用推奨。nonce(OpenID Connect 利用時):ID トークンのリプレイ対策。- ミックスアップ攻撃対策:複数の認可サーバーと連携する場合、認可レスポンスの
issパラメータ(RFC 9207) を検証するか、認可サーバーごとに異なるリダイレクト URI を使う。
5. アクセストークン
- 短命にする(目安:数分〜1時間)。
- スコープは最小権限。 必要なものだけ要求し、リソースサーバーは毎回スコープを検証する。
- オーディエンスを制限する(
audクレーム、または Resource Indicators(RFC 8707))。あるリソース向けトークンを別のリソースで使えないようにする。 - URL のクエリパラメータでトークンを渡さない。
Authorization: Bearerヘッダーを使う。 - 送信者制約(Sender-Constrained Token) を可能なら導入する:
- DPoP(RFC 9449):アプリケーション層で公開鍵に紐付け。SPA・モバイル向き。
- mTLS(RFC 8705):クライアント証明書に紐付け。サーバー間向き。
JWT アクセストークンを検証する場合
- 署名を必ず検証し、
alg: noneを拒否。許可するアルゴリズムを固定する。 iss/aud/exp/nbf/iatを検証する。- 鍵は JWKS から取得し、
kidで選択、キャッシュとローテーションに対応する。 - 即時失効が必要ならイントロスペクション(RFC 7662)を併用する。
6. リフレッシュトークン
- パブリッククライアントでは以下のいずれかが必須:
- リフレッシュトークンローテーション(使用のたびに新しいものを発行し、旧トークンは無効化)
- 送信者制約(DPoP / mTLS)
- ローテーション時、使用済みトークンの再利用を検知したら、そのトークンファミリー全体を失効させる(漏えいのシグナル)。
- 絶対有効期限とアイドルタイムアウトを設定する。
- ログアウト・パスワード変更・権限剥奪時に失効させる(Token Revocation(RFC 7009))。
7. クライアント認証(コンフィデンシャルクライアント)
client_secretよりも強い方式を優先:private_key_jwt(非対称鍵による JWT クライアントアサーション)- mTLS(
tls_client_auth/self_signed_tls_client_auth)
client_secretを使う場合は十分な長さの乱数、Secret Manager で管理、定期ローテーション。- シークレットをフロントエンド・モバイルアプリ・リポジトリに含めない。
8. クライアント種別ごとの指針
SPA(ブラウザアプリ)
- 推奨:BFF(Backend for Frontend)パターン。 トークンはサーバー側で保持し、ブラウザには
HttpOnly; Secure; SameSiteCookie のセッションだけを渡す。 - BFF を使えない場合:
- Authorization Code + PKCE
- トークンはメモリ保持(
localStorageは XSS で盗まれるため避ける) - リフレッシュトークンはローテーション必須、DPoP があればなお良い
- 厳格な CSP で XSS リスクを下げる。
ネイティブアプリ(RFC 8252)
- 認可は外部ブラウザ / システムブラウザ(ASWebAuthenticationSession、Custom Tabs)で行う。埋め込み WebView は使わない(資格情報を盗み見られる・SSO が効かない)。
- リダイレクトは Claimed HTTPS URI(Universal Links / App Links) を優先。カスタム URL スキームは他アプリに乗っ取られうるため PKCE 必須。
- トークンは Keychain / Keystore に保存。
サーバーサイド Web アプリ
- コンフィデンシャルクライアントとして構成し、PKCE も併用。
- トークンはサーバー側セッションに保存し、ブラウザに出さない。
9. 認可サーバー側の設定
- 認可コードは一度だけ使用可能・短命(目安:〜60秒〜10分以内)。再利用を検知したら、そのコードで発行したトークンを失効。
- 登録時にクライアントが使えるグラントタイプ・スコープ・リダイレクト URI を制限する。
- PAR(Pushed Authorization Requests, RFC 9126) の利用を検討:認可リクエストのパラメータをバックチャネルで送り、改ざん・漏えいを防ぐ。
- 高セキュリティ用途(金融・医療など)は FAPI 2.0 プロファイルに準拠する。
- メタデータを公開する(RFC 8414
/.well-known/oauth-authorization-serverまたは OIDC Discovery)。 - 同意画面にはクライアント名・要求スコープを明示し、ユーザーが判断できるようにする。
- 認可エンドポイントにクリックジャッキング対策(
frame-ancestors/X-Frame-Options)を入れる。
10. 認証と認可を混同しない
- OAuth は認可の仕組み。ログイン(認証)に使うなら OpenID Connect を使い、ID トークンを検証する。
- アクセストークンを「ログインの証拠」として扱わない(別クライアント向けに発行されたトークンを持ち込まれる「トークン置換」攻撃の原因)。
- ユーザー識別には
sub(+iss)を使い、メールアドレスを一意キーにしない。
11. 運用・監視
- 実績のある認証ライブラリ / IdP を使い、自前実装は最小限にする。
- トークン発行・リフレッシュ・失効・失敗をログに残す(トークン本体はログに出さない)。
- 異常検知:リフレッシュトークン再利用、短時間の大量発行、不審な IP・地域。
- 署名鍵・クライアントシークレットのローテーション手順を用意する。
- 依存ライブラリの脆弱性情報を追う。
チェックリスト
- Implicit / ROPC を使っていない
- 全クライアントで PKCE(S256)を使っている
- リダイレクト URI を完全一致で検証している
-
state(OIDC ならnonce)を検証している - 複数 AS 連携時に
issを検証している - アクセストークンは短命・最小スコープ・オーディエンス制限あり
- トークンを URL に載せていない
- リフレッシュトークンはローテーション or 送信者制約あり、再利用検知あり
- コンフィデンシャルクライアントは
private_key_jwt/ mTLS を検討した - SPA は BFF または同等の保護、トークンを
localStorageに置いていない - ネイティブアプリは外部ブラウザを使い、WebView を使っていない
- JWT 検証で
alg・iss・aud・expを確認している - ログインには OIDC の ID トークンを使っている
- トークン・シークレットをログやリポジトリに出していない
参考仕様
| 仕様 | 内容 |
|---|---|
| RFC 6749 | OAuth 2.0 本体 |
| RFC 6750 | Bearer Token の使い方 |
| RFC 7636 | PKCE |
| RFC 8252 | ネイティブアプリ向け OAuth |
| RFC 8628 | Device Authorization Grant |
| RFC 7009 | Token Revocation |
| RFC 7662 | Token Introspection |
| RFC 8414 | 認可サーバーメタデータ |
| RFC 8705 | mTLS クライアント認証・証明書バインドトークン |
| RFC 8707 | Resource Indicators |
| RFC 9068 | JWT アクセストークンのプロファイル |
| RFC 9126 | PAR |
| RFC 9207 | 認可レスポンスの iss パラメータ |
| RFC 9449 | DPoP |
| RFC 9700 | OAuth 2.0 Security Best Current Practice |
| draft-ietf-oauth-v2-1 | OAuth 2.1 |
| draft-ietf-oauth-browser-based-apps | ブラウザアプリ向けベストプラクティス |
| FAPI 2.0 | 高セキュリティ用途向けプロファイル(OpenID Foundation) |