ガイドSC

SC科目B(午後)のOAuth 2.0認可コードとトークン保護

公開: 2026-09-25更新: 2026-09-26
OAuth 2.0のAuthorization Code、PKCE、Access Token、Refresh Token、state、redirect_uriを一つの連携事例で解説。正常ログ、異常ログ、漏えいと失効後の確認まで学ぶ。

OAuth 2.0の認可コードフローは、利用者のパスワードを連携先アプリへ渡さず、限定したAPI利用権限を委譲する仕組みです。科目Bでは、認可要求、コールバック、トークン交換、API利用、失効を別々のログとして読み、その間にある攻撃条件を問われます。この記事は架空の請求書サービスを一例に、Authorization Code、PKCE、Access Token、Refresh Token、state、redirect_uriを時系列で結びます。

最初に登場人物と正常な通信を確認し、次に各値の照合条件を読みます。その後、コールバックの偽造、認可コードの横取り、トークンの漏えい、Refresh Tokenの再利用を比較し、失効後の確認まで追います。billing.example、partner.example、すべてのID・時刻・ログは説明用の架空例です。

1. 連携の目的と四つの役割

請求書サービスbilling.exampleは、経理担当者Aの許可を得て、外部の文書サービスにある請求書ファイルを読みます。Aは文書サービスのパスワードをbilling.exampleへ渡しません。文書サービス側が発行する限定的な権限で、billing.exampleがAPIを呼びます。ここでのOAuth 2.0はAPIへの委譲を扱います。利用者の本人確認結果をクライアントがログインに使う話は、別テーマのOpenID Connectで扱います。

  • Resource Owner(リソース所有者):経理担当者A。文書サービス上のファイルへのアクセスを許可・取り消しできる主体です。

  • Client(クライアント):billing.exampleのサーバ。認可コードを受け取り、トークンを保管してAPIを呼びます。client_idは識別子であり、秘密情報ではありません。

  • Authorization Server(認可サーバ):auth.partner.example。Aの認証と同意を扱い、認可コードとトークンを発行・失効します。

  • Resource Server(リソースサーバ):api.partner.example。Access Tokenを検証し、許された範囲のファイルAPI要求に応答します。

この事例ではbilling.exampleのサーバがClient SecretとRefresh Tokenをサーバ側に保管できるため、コンフィデンシャルクライアントとします。ブラウザに秘密を置くSPAやモバイルアプリは公開クライアントで、Client Secretを持つ設定にしても秘密を守れません。どちらでも認可コードとPKCEを使う設計を基本にします。

次の図は、四者の通信と、認可サーバ・リソースサーバの役割の違いを示します。ブラウザは認可要求とコールバックを運びますが、トークンはこの例ではサーバ間で交換します。

請求書サービスのOAuth連携架空構成。利用者ブラウザは認可サーバとの画面遷移を担い、billing.exampleのサーバがトークンを交換・保管する。利用者・アプリ文書サービス123Aのブラウザ請求書サービス認可サーバファイルAPI
請求書サービスのOAuth連携
  1. 1. 認可要求・code受領
  2. 2. コード交換・失効
  3. 3. Access TokenでAPI要求

架空構成。利用者ブラウザは認可サーバとの画面遷移を担い、billing.exampleのサーバがトークンを交換・保管する。

認可サーバが発行したトークンを、リソースサーバが受け入れる構成です。billing.exampleがAPIへ到達できた事実だけで、Aがどの画面から操作したかや、ファイルの所有者確認が適切だったかは分かりません。scopeは許可する操作の上限であり、ファイル単位の認可はリソースサーバ側でも必要です。

1-1. 事前登録と信頼境界

text
client_id=billing-prod
client_type=confidential
registered_redirect_uri=
  https://billing.example/oauth/callback
requested_scope=files:read
authorization_endpoint=
  https://auth.partner.example/authorize
token_endpoint=
  https://auth.partner.example/token
resource_endpoint=
  https://api.partner.example/files

これは架空の事前設定です。認可サーバはclient_idと登録済みredirect_uriを対応付けます。要求のredirect_uriを攻撃者のURLへ変えられると認可コードの送付先が変わるため、原則として登録値との完全一致で照合します。ネイティブアプリのlocalhostポートには規定上の例外がありますが、このWebサーバの例には当てはめません。

2. Authorization Codeフローを一往復で追う

Clientは認可開始時に、推測しにくいstateとPKCE用のcode_verifierを取引ごとに作ります。stateはブラウザの開始セッションに結び付けて保持し、code_verifierはサーバ側に保管します。code_verifierから作ったcode_challengeだけを認可要求へ入れます。

次の図の1〜9は主な通信順です。認可コードはブラウザ経由でClientに戻り、Access TokenとRefresh TokenはToken EndpointからClientへ戻ります。認可コードもトークンも、この事例では短い表示用IDで示します。

認可コードとPKCEの通信順架空の四者間フロー。ブラウザはcodeを返すが、Clientのサーバ側がcode_verifierを使ってトークンへ交換する。ブラウザClient認可サーバファイルAPI1. 連携開始2. 認可URLへ誘導3. 認可要求4. codeとstate5. callback6. codeとverifier7. ATとRT8. Bearer AT9. ファイル応答
認可コードとPKCEの通信順

架空の四者間フロー。ブラウザはcodeを返すが、Clientのサーバ側がcode_verifierを使ってトークンへ交換する。

図では認可画面でのAの認証・同意をブラウザと認可サーバの往復にまとめています。認可サーバがcodeを送る前にredirect_uriを検査し、Clientはcallbackでstateを検査します。Token Endpointではcodeとcode_verifierの対応に加え、Client認証、codeの有効期限・未使用、client_id、redirect_uriとの結び付きを検査します。

2-1. 正常な要求・応答を読み分ける

次は架空のHTTP要約ログです。秘密値とトークン本文は記録せず、state_ref、code_ref、token_fpという調査用の参照値だけ残しています。実際のstateやcodeはこの短い文字列ではなく、十分に推測しにくい値です。

text
09:00 Client auth_start session=web-41
  state_ref=S-7 verifier_ref=V-7
  challenge_method=S256 scope=files:read
09:01 Browser -> AS GET /authorize
  response_type=code client_id=billing-prod
  redirect_uri=https://billing.example/oauth/callback
  state_ref=S-7 challenge_ref=C-7
09:02 AS consent=granted owner=A
09:02 AS -> Browser 302 Location=callback
  code_ref=CODE-7 state_ref=S-7
09:02 Client callback session=web-41
  state_match=true state_consumed=true
09:02 Client -> AS POST /token
  grant_type=authorization_code code_ref=CODE-7
  redirect_uri=https://billing.example/oauth/callback
  verifier_ref=V-7 client_auth=success
09:02 AS token_response pkce_s256=match
  token_type=Bearer scope=files:read
  access_fp=AT-1 refresh_fp=RT-1 expires_in=600
09:03 Client -> RS GET /files/8101
  Authorization: Bearer <access_token>
09:03 RS response=200 scope=files:read

09:02のcallbackでstateが一致し、一度だけ消費されています。認可コードCODE-7とverifier V-7の交換に成功し、09:03にAccess TokenでAPIを呼んでいます。Access Tokenが600秒で期限切れになる例ですが、Refresh Tokenの期限はこの応答だけでは分かりません。APIの200はこの要求の成功を示すだけで、他のファイルの権限まで保証しません。

2-2. 認可要求とToken Endpointの主要フィールド

  • response_type=code:認可エンドポイントからAccess Tokenを直接返さず、認可コードを返す指定です。codeはClientとredirect_uriに結び付け、短命・一回限りで扱います。

  • client_id:登録されたClientの公開識別子です。client_idが分かることはClient認証に成功したことを意味しません。

  • scope=files:read:要求するAPI操作の範囲です。認可サーバは要求より狭いscopeを発行する場合があり、Clientは実際に返ったscopeを確かめます。

  • redirect_uri:認可結果を戻すClientのURLです。認可要求時の値がToken Endpointで必要な構成では、交換時にも同じ値を送り、認可サーバがcodeとの対応を検査します。

  • grant_type=authorization_code、code、code_verifier:Token Endpointで認可コードをトークンへ交換するための値です。Client認証はコンフィデンシャルクライアントとして別に行います。

  • token_type=Bearer、expires_in、access_token、refresh_token:成功応答の例です。Refresh Tokenは常に発行されるわけではなく、発行方針とscopeを確認します。

3. state:開始したブラウザとcallbackを結び付ける

stateはClientが認可開始時に作り、認可要求へ入れ、認可サーバが応答で返す値です。Clientはブラウザの開始セッションにひも付けた値と、callbackの値を比較して一回だけ消費します。別セッション、欠落、不一致、再利用なら認可コードを交換しません。値を比較するだけで開始セッションに結び付けなければ防御になりません。

この事例ではstateでcallbackへのCSRF、特に攻撃者が自分の認可コードを被害者のブラウザへ送り込み、被害者のClientセッションを攻撃者の文書アカウントと結び付ける攻撃を防ぎます。PKCEが適切に使われ、認可サーバの対応も確認済みなら、PKCEをCSRF対策に使える場合があります。この記事のClientは調査しやすい明示的なstate検査も行います。

text
09:10 Client auth_start session=web-41
  expected_state_ref=S-8
09:11 Client callback session=web-41
  received_state_ref=S-ATTACK code_ref=CODE-X
  state_match=false exchange_attempted=false
09:12 Client callback session=web-41
  received_state_ref=S-8 code_ref=CODE-8
  state_match=true state_consumed=true
09:13 Client callback session=web-41
  received_state_ref=S-8 code_ref=CODE-8
  state_reuse=true exchange_attempted=false

09:11は現在の認可取引と一致しないため拒否され、09:13は同じstateの再利用として拒否されました。このログだけでは攻撃者が誰か、CODE-Xが有効かは分かりません。callbackの生URLにcodeやstateを長期間残さず、ログには照合結果と参照IDだけを残します。

stateに連携後の戻り先を入れる場合も、攻撃者が任意のURLを指定できる設計にしません。サーバ側で固定した戻り先へ対応付けるなどし、Client自身がopen redirectにならないようにします。stateを暗号化しただけで利用者セッションとの結び付きが成立するわけではありません。

4. PKCE:認可コードを開始したClientだけが交換する

PKCE(Proof Key for Code Exchange)ではClientが認可取引ごとに推測しにくいcode_verifierを作り、S256方式でcode_challengeを計算します。認可サーバはchallengeを認可コードへひも付けます。Token EndpointでClientがverifierを送ると、認可サーバは同じ変換結果か検査します。URLのcodeだけを盗んでも、verifierがなければ交換できません。

S256の計算はBASE64URL-ENCODE(SHA256(ASCII(code_verifier)))です。base64urlでは末尾のパディングを付けません。次の値はRFC 7636の計算例であり、実運用の秘密値ではありません。ログにverifierを残す例でもありません。

text
code_verifier=
  dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
code_challenge_method=S256
code_challenge=
  E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM

verifierを認可要求へそのまま送るplain方式も仕様にはありますが、この構成ではS256を使用します。Client SecretはClient自体を認証するための別の秘密で、PKCEの取引ごとの結び付きと置き換えられません。公開クライアントはClient Secretを秘匿できないため、PKCEの必要性が特に高くなります。

4-1. codeを横取りしただけの攻撃と、PKCEの限界

text
09:20 AS issued code_ref=CODE-9
  client_id=billing-prod challenge_ref=C-9
09:21 unexpected POST /token code_ref=CODE-9
  verifier_ref=V-X pkce_s256=mismatch
  result=invalid_grant token_issued=false
09:22 Client POST /token code_ref=CODE-9
  verifier_ref=V-9 pkce_s256=match
  result=success code_consumed=true

09:21にcodeを持った別の要求が来ましたが、verifierが一致せずトークンは発行されませんでした。09:22の正規交換が成功したことから、この架空ログでは09:21にcodeが消費されていません。実装ごとの無効化方針やレート制限は別に確認します。codeとverifierの両方、またはClientのサーバ自体が侵害された場合、PKCEだけでは守れません。

認可サーバがPKCEに対応しているはずなのにchallengeなしの要求を許したり、codeに結び付けた方式をToken Endpointで検査しなかったりすると、verifier検証を迂回できます。ClientはPKCEが実際に適用されたことを設定・拒否テストで確認します。

5. redirect_uri:認可コードの配送先を固定する

redirect_uriは認可サーバが認可結果を送る宛先です。認可サーバはClient登録時のURIと認可要求のURIを照合し、一致しない要求を認可画面へ進めません。Webサーバのこの例ではscheme、host、pathを含む登録済み文字列と完全一致させます。前方一致、ワイルドカード、似たドメインの許可はcodeの漏えいにつながります。

text
registered:
  https://billing.example/oauth/callback
request-1:
  https://billing.example/oauth/callback
  -> accepted
request-2:
  https://billing.example.evil.example/oauth/callback
  -> rejected: redirect_uri_mismatch
request-3:
  https://billing.example/oauth/callback/next
  -> rejected: redirect_uri_mismatch

request-2はbilling.exampleに似た文字列を含みますが、hostは別です。request-3も登録値と異なります。認可サーバがrequest-2を許す設定なら、攻撃者がAを認可画面へ誘導した際にcodeを攻撃者のサイトへ送れてしまいます。ただしcodeの取得だけで直ちにAPIを使えるとは限らず、PKCEのverifierと、この事例ではClient認証も必要です。

Clientのcallback自身が任意の外部URLへ転送するopen redirectでも、codeやstateを含むURLが漏れる経路になります。認可サーバのURI照合と、Client側の転送・Referer・アクセスログの設定を別々に点検します。認可要求時にredirect_uriを指定した場合は、Token Endpointで同じ値とcodeの対応も確認します。

5-1. state、PKCE、redirect_uriの担当を混同しない

  • state:Clientが『このブラウザが開始した認可取引か』をcallbackで照合します。認可サーバが返したcodeを正規セッションへ誤って結び付けることを防ぎます。

  • PKCE:認可サーバが『このcodeの交換者は、開始時のverifierを知るか』をToken Endpointで照合します。code単独の横取り・注入を抑えます。

  • redirect_uri:認可サーバが『codeを登録済みの配送先へ戻すか』を認可要求時に照合します。Client側のopen redirectも別に塞ぎます。

  • Client認証:コンフィデンシャルクライアントがToken Endpointで自身を認証します。client_idの提示やPKCEの成功とは別の条件です。

一つの防御が成功しても他の設定不備を帳消しにはできません。例えばstateが正しくても、認可サーバが攻撃者のredirect_uriを許せばcodeを送ってしまいます。PKCEはcode交換を守れますが、既に発行されたBearer Access Tokenの漏えいを防ぐ仕組みではありません。

6. Access Token:APIが受け付ける権限と有効期間

Access Tokenはリソースサーバへ提示する権限の証票です。この事例ではBearer Tokenなので、値を知る者が有効期間中に使える前提で保護します。Token Endpointで受け取ったClientはTLSでAPIへ送り、Authorizationヘッダに置きます。URLのクエリ、Referer、画面上のJavaScript、通常ログへ出さない設計にします。

text
GET /files/8101 HTTP/1.1
Host: api.partner.example
Authorization: Bearer <access_token>

RS decision 09:03 request=req-301
  token_fp=AT-1 active=true
  audience=api.partner.example scope=files:read
  owner=A file=8101 authorized=true
  response=200

このログではリソースサーバがトークンの有効性、対象API、scopeに加え、Aがファイル8101を読めることを確認したとします。ただしactiveやaudienceの検査方法は、トークンが不透明な値か自己完結型かで異なります。Access Tokenは必ずJWTという意味ではなく、文字列を分解して勝手に信頼してはいけません。

  • 不透明なトークン:リソースサーバは認可サーバへの照会や共有された状態から有効性・scopeなどを確認します。照会を行う構成では失効情報を反映しやすい一方、応答遅延やキャッシュを考えます。

  • 自己完結型トークン:リソースサーバが署名、発行者、宛先、有効期限、scopeなどを検査する構成があります。形式と検証規則は採用したプロファイルに従います。署名が正しくても所有ファイルの認可は別です。

  • scopeとaudience:files:readは操作範囲、audienceは利用先のAPIを限定します。別のAPIや、Aが見られないファイル9102で成功する根拠にはなりません。

トークンの短い寿命と狭いscopeは漏えい時の影響を抑えますが、既に漏れたBearer Tokenの即時無効化を保証しません。自己完結型トークンをAPIが失効照会せずに検証していれば、認可サーバ側で失効処理をしても期限まで受け入れる可能性があります。API側の照会、拒否リスト、鍵やセッションの扱いを確認します。

7. Refresh Token:Access Tokenを更新する長寿命の権限

Refresh TokenはClientがToken Endpointへ送り、新しいAccess Tokenを得るための値です。リソースサーバの通常APIへは送りません。認可サーバが発行するかどうかはポリシー次第です。発行するならClient、許可されたscope、対象リソースへの結び付きを維持し、保存先を厳しく管理します。

text
09:12 Client -> AS POST /token
  grant_type=refresh_token refresh_fp=RT-1
  client_auth=success
09:12 AS response access_fp=AT-2
  expires_in=600 expires_at=09:22
  refresh_fp=RT-2 rotation=success
  RT-1 status=invalidated
09:13 Client -> RS GET /files/8101
  bearer_fp=AT-2 result=200

この架空例はRefresh Token Rotationを採用しています。RT-1を使ったらRT-2を発行し、RT-1を無効にしました。ClientはRT-2を安全に保存し直す必要があります。認可サーバがRotationを使わない構成もあり、public clientでは再利用を検知するためにRotationか送信者拘束などが求められます。

次は正常なRT-1→RT-2更新とは別の連携取引です。このClientではToken EndpointのClient認証も必要です。正規ClientがRT-10をRT-11へ更新した後、旧RT-10が再提示されました。攻撃者による更新なら、RT-10に加えてClient Secretの入手や正規Clientの悪用が必要です。認可サーバのログだけでは再提示した側を特定できません。

text
09:12 Client refresh family=F-10 presented=RT-10
  client_auth=success accepted=true
  issued=RT-11 RT-10=invalidated
09:14 AS refresh family=F-10 presented=RT-10
  client_auth=success accepted=false
  reason=rotated_token_reuse
  active_refresh=RT-11 revoked=true
09:15 Client refresh presented=RT-11
  result=invalid_grant reconnect_required=true

09:14はRT-10の再利用を示します。盗難による再利用が疑われますが、同じClientからの並列更新や保存失敗でも似たログになり得ます。Client認証の成功だけでは要求者の身元を特定できません。要求元と保存処理を調べ、関連する有効なRTを失効させます。既発行のATが直ちに拒否されるかは別に確認します。

7-1. 保存、期限、再連携

  • サーバ側保管:Refresh Token、Client Secret、PKCE verifierをブラウザへ渡さず、暗号化・アクセス制限・監査のある領域へ置く。表示用ログにはトークン本文を残さない。

  • 有効期間:Access TokenとRefresh Tokenの寿命は別です。Refresh Tokenに無活動期限や絶対期限がある構成では、切れたら再認可が必要です。

  • 更新失敗:ネットワーク障害とinvalid_grantを区別する。後者を無限再試行すると、失効済み系列の再利用を繰り返す可能性があります。

  • 連携解除:アプリ内の資格情報を消すだけでなく、認可サーバの失効・同意解除を行い、関連Access Tokenがいつ拒否されるかを確認する。

8. 失効と漏えい:どのトークンが、いつ使えなくなるか

次の事象を一つのインシデントとして考えます。billing.exampleのプロキシが誤ってAuthorizationヘッダをログに残し、AT-2の値が閲覧可能なログ保管先へ入っていました。これはAccess Tokenの露出です。ログを閲覧した者が実際にAPIを呼んだかは、その時点では未確認です。

text
09:16 proxy_config=log_authorization_header:on
  request=req-316 token_fp=AT-2
  log_store=shared-ops retention=30d
09:18 alert=secret_pattern_in_log
  token_fp=AT-2 exposed_to=log_readers
09:20 RS request=req-320 token_fp=AT-2
  source_ip=203.0.113.45 response=200
  file=8101 actor_binding=unknown

09:16〜09:18のログはAT-2がログ保管先に露出したことを裏付けます。09:20は同じトークン指紋を使ったAPI要求ですが、このIPや指紋だけで攻撃者の身元は確定しません。正規Clientの送信元情報、プロキシログ、APIの監査記録を合わせ、どのファイルを返したかを調べます。

次の図は、漏えいの疑いが出た後の作業順です。トークンを止める判断と、既に行われたAPI操作の調査を並行して進めます。

トークン露出後の対応と証拠架空事例。露出を止め、対象トークンと関連するRefresh Tokenを評価し、APIの拒否と正規連携の復旧を確かめる。1露出を止める2失効を依頼3利用を調べる4連携を復旧
トークン露出後の対応と証拠
  1. 1. 露出を止める:対応 ログ設定と閲覧権限を修正/確認する証跡 変更履歴
  2. 2. 失効を依頼:対応 ATとRTの範囲を確認/確認する証跡 AS応答と方針
  3. 3. 利用を調べる:対応 API要求とファイルを照合/確認する証跡 要求IDと監査ログ
  4. 4. 連携を復旧:対応 再認可と拒否を確認/確認する証跡 新旧トークンの結果

架空事例。露出を止め、対象トークンと関連するRefresh Tokenを評価し、APIの拒否と正規連携の復旧を確かめる。

最初にログへの記録を止め、既存ログの閲覧権限と保管期間を見直します。次に認可サーバへAT-2の失効を依頼し、Refresh Tokenも露出した可能性があればその系列と同意を調べます。クライアントの秘密やサーバ自体が漏れた疑いがあれば、その再発行も別に判断します。

8-1. 失効APIの200とAPI側の拒否は別の証拠

RFC 7009の失効APIは、正常な失効要求にも、存在しないトークンを指定した要求にもHTTP 200を返します。したがって200だけで『指定した値が実在した』『全APIが既に拒否する』とは言えません。Clientの認証、指定値の種類、認可サーバの失効方針と伝播時間も見ます。

text
09:21:00 Client -> AS POST /revoke
  token_fp=AT-2 token_type_hint=access_token
  client_auth=success result=200
  AT-2 expires_at=09:22:00
09:21:05 RS GET /files/8101 bearer_fp=AT-2
  result=200 reason=local_token_cache
09:21:25 RS cache_sync=revocation_received
09:21:30 RS GET /files/8101 bearer_fp=AT-2
  result=401 reason=inactive_token
09:21:35 Client -> AS refresh_fp=RT-2
  result=success issued=AT-3
09:21:45 Client GET /files/8101 bearer_fp=AT-3
  result=200 authorized_file=true

AT-2の期限は09:22です。失効要求直後の09:21:05はキャッシュで受け入れられ、失効情報を反映した後の09:21:30に拒否されました。両要求とも期限前なので、自然な期限切れとは区別できます。RT-2はこの構成では失効対象に含めず、AT-3を得ました。AT-3による200は正規連携の復旧を示しますが、漏えい範囲の調査完了は示しません。

Refresh Tokenの失効が関連Access Tokenに波及するか、Access Tokenの失効がRefresh Tokenに波及するかは認可サーバの方針に依存します。自己完結型Access Tokenをリソースサーバがオフライン検証する場合は、失効情報を参照させる設計か短い残存寿命を確認します。旧ATはAPIでの拒否、旧RTはToken Endpointでの更新拒否を分けて確認します。

8-2. 復旧と影響範囲の確認

  1. 露出した場所と期間を確定する。プロキシ設定、ログ転送先、閲覧権限、保持期間、削除・隔離の履歴を保存する。

  2. 対象トークンと利用権限を特定する。指紋からClient、scope、対象API、発行・期限時刻を調べ、本文は調査記録へ転載しない。

  3. 失効と遮断を確認する。旧ATは対象のリソースサーバで拒否を確認する。旧RTは認可サーバの失効状態を調べ、必要なら検証環境のToken Endpointで更新拒否を確かめる。

  4. APIの利用を調べる。要求ID、時刻、送信元、ファイルID、応答、監査ログから実際に返ったデータを特定し、単なる試行と成功を分ける。

  5. 再連携する。必要ならAの同意を取り直し、サーバ側で新しいトークンを保存する。正規のfiles:read操作が成功し、不要なscopeと旧トークンは使えないことを確かめる。

Refresh Tokenが漏れ、攻撃者がClient認証も通せる場合は、新しいAccess Tokenを発行され得るため系列失効と再認可が中心です。この事例ではRTだけを盗んでもClient Secretなしでは更新できません。Access Tokenだけの露出なら、期限・scope・実利用を調べます。どちらも失効APIの200だけを終了条件にしません。

9. 科目Bのログを時系列で切り分ける

一つのログだけで原因を決めず、認可開始、認可サーバのcode発行、Clientのcallback、Token Endpoint、API、失効の順に要求IDや参照IDをつなぎます。以下は同じ架空連携で起きた四つの異常です。出力のフィールドは説明用で、OAuth 2.0が全製品に同じログ形式を要求するわけではありません。

9-1. callbackの不一致とcode交換の失敗を分ける

text
case=A 10:00 callback state_match=false
  code_ref=CODE-A exchange_attempted=false
case=B 10:10 callback state_match=true
  code_ref=CODE-B exchange_attempted=true
  AS result=invalid_grant pkce_s256=mismatch
case=C 10:20 AS /authorize result=invalid_request
  reason=redirect_uri_mismatch code_issued=false
case=D 10:30 RS GET /files/8101 result=401
  token_fp=AT-D reason=expired
  • A:Clientがstate不一致で止めた。codeが有効か、攻撃者が送ったかは、この行だけでは確定しない。Clientの開始セッションとcallbackの取得元を調べる。

  • B:Clientはcallbackを受けたが、認可サーバがverifier不一致でトークン発行を拒否した。code横取り・混同、Clientのverifier保存ミス、取引取り違えを比較する。

  • C:認可サーバが配送先を拒否し、codeは発行していない。登録値と要求値を照合し、正規変更か不正変更かを調べる。

  • D:リソースサーバでAccess Tokenの期限切れを検出した。Refresh Tokenの有効性やClientの更新失敗は、Token Endpoint側のログを見なければ分からない。

Bのinvalid_grantはPKCE以外にもcodeの期限切れ、再利用、redirect_uri不一致などで起こり得ます。この例ではpkce_s256=mismatchという追加証拠があるため原因を絞れます。エラー名だけを暗記せず、どのサーバが何を比較したかを読むことが重要です。

9-2. 失効の結果と実利用の結果を分ける

text
case=E AT-E expires_at=11:20
  11:00 AS /revoke token_fp=AT-E -> 200
  11:00 RS token_fp=AT-E -> 200 cache=old
  11:03 RS token_fp=AT-E -> 401
case=F 11:10 AS refresh family=F-11
  old_rt_reused=true active_rt_revoked=true
  11:12 RS bearer_fp=AT-F -> 200

Eは失効APIの成功応答と、リソースサーバの反映時刻が違う例です。FはRefresh Token系列を止めても、既発行のAccess Tokenがまだ通る例です。Fの後に『連携が完全に遮断された』と答えるには、AT-Fの失効または期限切れと、対象APIでの拒否を確認する必要があります。

調査ではtoken_fpや要求IDを使ってログを結びます。ただし異なるシステムの時計がずれている場合は、時刻だけで前後関係を決めません。各サーバの時刻同期、要求IDの伝播、ログ取得範囲、欠損を確認します。トークン本文を調査用ログにコピーしない運用も重要です。

10. 運用変更で崩れやすい前提

  • redirect_uriの変更:本番・検証のcallbackを別々に登録し、移行時は登録値、Client設定、実際の認可要求と交換要求を照合する。広いワイルドカードで移行を済ませない。

  • 複数の認可サーバ:同じClientが複数事業者と連携するなら、返ってきたcodeの発行元を取り違えるmix-up攻撃への対策を設計する。発行者識別子issや事業者ごとに異なるcallbackを使う。

  • 公開クライアント:SPA・モバイルアプリにはClient Secretを埋め込んでも秘匿できない。PKCEを前提に、Refresh Tokenを発行するか、回転・送信者拘束・保管方式を検討する。

  • 権限の変更:files:readに加えて書込みを求めるなら、Clientと認可サーバの設定、Aの同意、実際に発行されたscope、API側の権限を再確認する。古い同意を無条件に拡大しない。

  • Clientの撤去:画面から連携設定を消すだけで終えず、認可サーバの同意・Refresh Token系列、残存Access Token、保存済み秘密、監査ログの保持を確認する。

OAuthで得たAccess Tokenは、ClientがAPIを使うためのものです。Clientが利用者のログインを実装するなら、認証を意図したOpenID ConnectのID Tokenやセッション設計を別に確認します。Access Tokenに利用者らしい文字列が見えても、それをそのままログインの証明に流用しません。

Implicit GrantでAccess Tokenをブラウザの認可応答へ直接返す設計や、利用者のパスワードをClientへ預ける方式は、現在のセキュリティ推奨に沿いません。この教材の連携は認可コードとPKCEを使います。過去の構成図で古い方式を見た場合も、どこで値が露出するかを区別して評価します。

11. 科目B(午後)の記述演習

各問で、観測された事実、考えられる原因、追加で必要な証拠、対策を分けて答えてください。認可サーバとClientとリソースサーバのどこで拒否されたかを先に特定します。

演習1:state不一致のcallback

条件:AのブラウザのcallbackにはCODE-Xが付き、Clientのログはstate_match=false、exchange_attempted=falseです。問い:分かること、攻撃の成立条件、Clientの対処を答える。

解答:Clientは現在のブラウザが開始した取引と一致しない応答を拒否し、codeを交換していません。攻撃者が別取引のcodeを注入するには、被害者にそのcallbackを開かせるなどの経路が必要です。Clientはセッションへ結び付けた一回限りのstateを照合し、不一致なら交換せず記録します。

誤答の理由:『codeが付いているので攻撃者はAccess Tokenを得た』は交換結果を無視しています。『stateを暗号化すれば照合不要』は開始取引との対応を確認していません。

演習2:PKCEで拒否されたcode

条件:認可サーバはCODE-9に対しpkce_s256=mismatch、invalid_grant、token_issued=falseを記録しました。問い:何を拒否し、何を追加調査するか。

解答:提示されたverifierがcodeに結び付いたchallengeと一致せず、トークン交換を拒否しました。Clientのverifier保存先と取引ID、codeを得た経路、同じcodeの成功した交換の有無を確認します。codeとverifierの両方が漏れていないかも調べます。

誤答の理由:『stateが一致していればPKCEは不要』は、code交換者の検証をしていません。『invalid_grantなら必ず攻撃』は保存ミスや期限切れなどの可能性を無視しています。

演習3:redirect_uriの照合

条件:登録値はhttps://billing.example/oauth/callbackで、要求値はhttps://billing.example.evil.example/oauth/callbackです。認可サーバはredirect_uri_mismatchを返しました。問い:理由と防いだ経路を答える。

解答:要求値のhostはevil.example側であり、登録済みURIと一致しません。拒否により、その宛先へ認可コードを配送する経路を止めています。Client側のopen redirectや別の登録URIも点検します。

誤答の理由:『billing.exampleを含むので同じドメイン』はhostの境界を読み違えています。『PKCEがあるので配送先の照合は不要』はcodeの露出経路を残します。

演習4:APIの200が意味する範囲

条件:RSはAT-1にactive=true、scope=files:read、file=8101、authorized=trueを記録し200を返しました。問い:確認できる事実と、まだ確定しないことを答える。

解答:その時点のRSがAT-1とAのファイル8101への読取りを許した事実です。AT-1が別のAPIやファイルにも使えるか、Aのブラウザ操作が正規だったか、トークンが他者に渡っていないかは分かりません。必要なら発行・Client・APIログを結びます。

誤答の理由:『scope=files:readなら全ファイルを読める』はファイル単位の認可を省いています。『200ならA本人が操作した』はBearer Tokenの代理利用を考慮していません。

演習5:Refresh Tokenの再利用

条件:Client認証が必要な構成です。RT-10でRT-11が発行された2分後、RT-10が再び提示され、認可サーバはfamily F-10の有効なRefresh Tokenを失効しました。問い:疑う事象、攻撃成立条件、復旧条件を答える。

解答:古いRT-10の再利用があり、盗難を疑います。攻撃者が更新するにはClient Secretの入手や正規Clientの悪用なども必要です。並列更新や保存失敗の可能性も調べ、系列失効と残るAccess Tokenの有効性を確認してから、Aの再認可と正規API利用を確認します。

誤答の理由:『RT-11を止めたから全Access Tokenも無効』は関連トークンの失効方針を確認していません。『再利用は必ず攻撃者』は正規Client側の競合を除外していません。

演習6:失効APIが200を返した

条件:AT-2の期限は09:22です。失効要求は09:21:00に200、RSは09:21:05に200、失効情報の反映後09:21:30に401でした。問い:この違いの説明と必要な再確認を答える。

解答:失効APIの200は全RSへの即時反映を証明しません。例のRSはキャッシュから一時的に受け入れ、失効情報の反映後に拒否しました。401は期限前なので自然な期限切れではありません。対象の各API、RT-2の状態、AT-2が使われた操作を確認します。

誤答の理由:『200が返れば漏えい対応完了』はRSでの受入れと既利用の調査を無視しています。『一度200なら失効APIは無効』は伝播差を考慮していません。

演習7:ログにBearer Tokenが残った

条件:プロキシがAuthorizationヘッダを共有ログへ記録し、同じtoken_fpのAPI要求が別の送信元から200になりました。問い:確定事実、追加調査、最初の対処を答える。

解答:トークンが共有ログへ露出し、同じ指紋のAPI要求が成功しています。別送信元の主体は未確定なので、Clientの正規送信元、ログ閲覧履歴、APIのファイルIDと応答を照合します。記録設定と閲覧権限を直し、対象トークンの失効とRS側の拒否を確認します。

誤答の理由:『IPが違うので必ず攻撃者』は経路変更やプロキシを考慮していません。『ログを消せば完了』は有効なトークンと過去のAPI操作を残します。

12. 一次資料

RFC 6749:OAuth 2.0の役割、認可コード、トークン

RFC 7636:PKCEとS256の計算・照合

RFC 9700:OAuth 2.0の現行セキュリティ推奨

RFC 6750:Bearer Tokenの使用と漏えい上の注意

RFC 7009:トークン失効の要求と応答

RFC 7662:不透明トークンなどの有効性照会

RFC 9207:複数認可サーバ利用時の発行者識別

この記事についてAIに深掘り質問する

ChatGPT、Claude、Perplexityにこの記事を参照させ、要点の確認や疑問点を自由に質問できます。

次におすすめの学習

編集・検証について

編集・検証:IT資格ラボ編集部

IPAが公開する試験要綱・シラバス・過去問題と、各技術の公式資料を優先して内容を確認しています。制度変更や誤りを確認した場合は、記事を見直して更新します。

編集方針・情報源・訂正方針を見る
この記事を共有する