OpenID Connect(OIDC)を安全に実装・運用するための推奨事項をまとめたもの。主に RP(Relying Party / クライアント)側の実装者を想定し、OP(OpenID Provider / 認可サーバー)側の設定についても触れる。
目次
- 前提:OIDC と OAuth 2.0 の関係
- フローの選択
- 認可リクエストのベストプラクティス
- リダイレクト URI
- ID Token の検証
- トークンの扱い
- クライアント認証
- ディスカバリーと鍵管理
- ログアウト
- スコープとクレームの最小化
- OP(認可サーバー)側の設定チェックリスト
- RP 実装チェックリスト
- よくあるアンチパターン
1. 前提:OIDC と OAuth 2.0 の関係
| 項目 | OAuth 2.0 | OpenID Connect |
|---|
| 目的 | 認可(API へのアクセス権の委譲) | 認証(ユーザーが誰かの確認) |
| 主な成果物 | Access Token | ID Token(JWT)+ Access Token |
| ユーザー情報 | 規定なし | UserInfo エンドポイント、標準クレーム |
- Access Token をログインの証明として使わない。 ユーザーの認証結果は必ず ID Token で確認する。
- OIDC は OAuth 2.0 の上に乗っているため、OAuth 2.0 のセキュリティ対策(RFC 9700)がそのまま適用される。
2. フローの選択
推奨:Authorization Code Flow + PKCE
- すべてのクライアント種別(Web サーバー、SPA、ネイティブアプリ)で Authorization Code Flow を使う。
- PKCE(RFC 7636)は機密クライアントでも必須扱いにする。認可コードの横取り・注入攻撃を防げる。
code_challenge_method は S256 を使う(plain は使わない)。
非推奨・使用禁止
| フロー | 状態 | 理由 |
|---|
Implicit Flow(response_type=token / id_token token) | 使わない | トークンが URL フラグメントに露出、リプレイ・漏洩リスク |
| Resource Owner Password Credentials | 使わない | クライアントがパスワードを直接扱う、MFA と相性が悪い |
| Hybrid Flow | 特別な理由がなければ不要 | 複雑化に見合うメリットが少ない。Code + PKCE で十分 |
用途別の補足
- マシン間通信(ユーザー不在):Client Credentials Grant(これは OIDC ではなく OAuth 2.0)。
- 入力制約デバイス(TV など):Device Authorization Grant(RFC 8628)。
- 高セキュリティ用途(金融・医療など):FAPI 2.0 Security Profile への準拠を検討する。
3. 認可リクエストのベストプラクティス
必須パラメータの正しい使い方
| パラメータ | 推奨 |
|---|
response_type | code |
scope | openid を必ず含める。必要最小限のスコープのみ要求 |
redirect_uri | 事前登録した値と完全一致させる |
state | 推測不能なランダム値。CSRF 対策。セッションに紐付けて検証 |
nonce | 推測不能なランダム値。ID Token のリプレイ対策。ID Token 内の値と照合 |
code_challenge / code_challenge_method | PKCE。S256 |
state と nonce は最低 128 bit 以上のエントロピーを持つ暗号論的乱数で生成する。
- PKCE を使っていても
state(またはそれに相当する CSRF 対策)と nonce は省略しない。
Pushed Authorization Requests(PAR, RFC 9126)
- 認可リクエストのパラメータをフロントチャネル(ブラウザ URL)ではなく、バックチャネルで OP に事前送信する。
- パラメータの改ざん・漏洩を防ぎ、URL 長の問題も解消できる。OP が対応しているなら利用を推奨。FAPI 2.0 では必須。
Mix-Up 攻撃対策
- 複数の OP と連携する RP では、認可レスポンスの
iss パラメータ(RFC 9207) を検証し、リクエストを送った OP からの応答であることを確認する。
- OP ごとに異なる
redirect_uri を使うのも有効。
4. リダイレクト URI
- 完全一致で照合する(ワイルドカード・部分一致・パターンマッチは使わない)。
- 本番では HTTPS 必須。
- オープンリダイレクトを持つページを
redirect_uri にしない。
- ネイティブアプリ(RFC 8252):
- プライベートスキーム(
com.example.app:/callback、逆ドメイン形式)か Claimed HTTPS スキーム(Universal Links / App Links)を使う。後者が望ましい。
- ループバック(
http://127.0.0.1:{port}/)はポート番号を可変として許容する。localhost ではなく IP リテラルを使う。
- 埋め込み WebView は使わず、システムブラウザ(ASWebAuthenticationSession / Custom Tabs)を使う。
5. ID Token の検証
ID Token は必ずすべて検証してから使う。ライブラリ任せにする場合も、以下が行われていることを確認する。
- 署名の検証
- OP の
jwks_uri から公開鍵を取得し、kid で鍵を選択。
alg は許可リストで固定する(例:RS256, PS256, ES256)。none は拒否。
- ヘッダーの
alg を盲目的に信用しない(アルゴリズム混同攻撃対策)。
iss:期待する OP の Issuer Identifier と完全一致。
aud:自分の client_id が含まれていること。複数含まれる場合は azp も確認。
exp:期限切れでないこと(時刻ずれの許容は数分程度に留める)。
iat:極端に古い/未来の値でないこと。
nonce:リクエスト時に送った値と一致すること。
auth_time / acr / amr:max_age や特定の認証強度(MFA など)を要求した場合は検証する。
ユーザーの識別
- ユーザーの一意識別には
iss + sub の組み合わせを使う。
email をキーにしない。 メールアドレスは変更・再利用され得るし、email_verified が false の場合もある。
- アカウントを
email で自動紐付けする場合は、email_verified=true かつ信頼できる OP に限定する。
6. トークンの扱い
保存場所
| クライアント種別 | 推奨 |
|---|
| サーバーサイド Web アプリ | トークンはサーバー側に保存。ブラウザにはセッション Cookie のみ |
| SPA | BFF(Backend for Frontend)パターンを推奨。トークンをブラウザ JS に渡さない |
| ネイティブアプリ | OS のセキュアストレージ(Keychain / Keystore) |
- SPA で BFF が使えない場合も、
localStorage への保存は XSS 時に窃取されるため避け、メモリ保持を基本とする。
- セッション Cookie は
Secure、HttpOnly、SameSite=Lax 以上、可能なら __Host- プレフィックスを付ける。
有効期限とリフレッシュ
- Access Token は短命(数分〜1時間程度)にする。
- Refresh Token は以下のいずれかで保護する:
- 公開クライアントでは Refresh Token Rotation(使用ごとに新しいトークンを発行し、旧トークンの再利用を検知したら一連のトークンを失効)。
- または 送信者制約(Sender-Constrained)トークン(後述)。
- Refresh Token には絶対的な有効期限とアイドルタイムアウトを設定する。
送信者制約トークン
トークン漏洩時の悪用を防ぐため、トークンを特定のクライアント鍵に紐付ける。
- DPoP(RFC 9449):アプリケーション層で鍵を証明。SPA・ネイティブでも使いやすい。
- mTLS(RFC 8705):クライアント証明書に紐付け。サーバー間通信向け。
その他
- トークンを URL クエリ・ログ・エラーメッセージに出力しない。
- Access Token の中身を RP 側で解釈しない(Access Token はリソースサーバー向け。RP にとっては不透明な文字列として扱う)。
7. クライアント認証
- 機密クライアントでは、共有シークレット(
client_secret_basic / client_secret_post)より非対称鍵による方式を推奨:
private_key_jwt
tls_client_auth / self_signed_tls_client_auth(mTLS)
client_secret を使う場合は十分な長さのランダム値とし、定期的にローテーションする。
- 公開クライアント(SPA・ネイティブ)にはシークレットを埋め込まない(秘匿できないため意味がない)。
8. ディスカバリーと鍵管理
- OP のメタデータは
/.well-known/openid-configuration から取得し、エンドポイントをハードコードしない。
- メタデータ内の
issuer が、取得元の Issuer と一致することを確認する。
- JWKS はキャッシュしつつ、未知の
kid が来たら再取得する(鍵ローテーション対応)。ただし再取得の頻度は制限して DoS を防ぐ。
- OP 側は署名鍵を定期的にローテーションし、新旧の鍵を一定期間並行公開する。
9. ログアウト
- 単にローカルセッションを消すだけでは OP のセッションは残る。要件に応じて以下を組み合わせる:
- RP-Initiated Logout:
end_session_endpoint に id_token_hint と post_logout_redirect_uri(事前登録済み)を付けてリダイレクト。
- Back-Channel Logout:OP からサーバー間で Logout Token を受け取り、該当セッションを破棄。サードパーティ Cookie 制限の影響を受けないため推奨。
- Front-Channel Logout:iframe ベースのため、サードパーティ Cookie 制限で動作しない場合が多い。新規採用は慎重に。
- Logout Token も ID Token と同様に署名・
iss・aud・iat・jti(リプレイ対策)を検証し、nonce が含まれていないことを確認する。
10. スコープとクレームの最小化
- 必要なスコープ・クレームだけを要求する(
profile や email を惰性で要求しない)。
- 取得した個人情報は必要な期間だけ保持し、ログに出力しない。
- 細かい要件がある場合は
claims パラメータで個別に要求する。
11. OP(認可サーバー)側の設定チェックリスト
12. RP 実装チェックリスト
13. よくあるアンチパターン
| アンチパターン | 問題 | 対策 |
|---|
| Access Token でログイン判定 | 別クライアント向けトークンでなりすまされる | ID Token を検証する |
| ID Token を API 呼び出しの Bearer に使う | ID Token は RP 向け。aud が API と一致しない | Access Token を使う |
email でユーザーを紐付け | 未検証メール・再利用メールによる乗っ取り | iss + sub を使う |
redirect_uri にワイルドカード | 認可コードの窃取 | 完全一致で登録 |
state を固定値や省略 | ログイン CSRF | ランダム値をセッションに紐付けて検証 |
| JWT のデコードだけして署名検証しない | 偽造トークンを受け入れる | 必ず署名を検証 |
SPA で localStorage にトークン保存 | XSS で窃取 | BFF パターン / メモリ保持 |
| 認可・認証処理を自前実装 | 検証漏れ・脆弱性 | 実績あるライブラリを使う |
参考仕様