OAuth 2.0 PKCE詳細と攻撃緩和

JavaScript

OAuth 2.0 PKCE詳細と攻撃緩和

モバイル/SPA向けOAuth 2.0 PKCE (RFC 7636) は、認可コード横取り攻撃に対するセキュリティ強化策として設計された。

背景

OAuth 2.0の認可コードフローは、通常、クライアント認証を要求する。しかし、モバイルアプリケーションやシングルページアプリケーション (SPA) といったPublic Clientは、クライアントシークレットを安全に保持することが困難である。このため、当初はImplicit Grantフローが利用されることがあったが、アクセストークンがリダイレクトURIのフラグメントとして直接返されるため、ブラウザ履歴、ログ、中間者攻撃に対して脆弱性が指摘された。この課題に対応するため、RFC 8252 (OAuth 2.0 for Native Apps) では、ネイティブアプリにおける認可コードフローとPKCEの利用が推奨されている。

設計目標

PKCE (Proof Key for Code Exchange) は、主にPublic ClientにおけるOAuth 2.0認可コードフローのセキュリティ強化を目的としている。具体的な設計目標は以下の通り。

  • クライアントシークレットを安全に保持できないPublic Client (例: モバイルアプリ、SPA) における認可コードフローのセキュリティ強化。
  • 認可コード横取り攻撃 (Authorization Code Interception Attack) の緩和。
  • 認可コードとアクセストークンの交換時に、クライアントが真正であることを検証するメカニズムの提供。

詳細

PKCEは、OAuth 2.0の認可コードフローにcode_verifiercode_challengeの2つのパラメータを導入することで、セキュリティを向上させる。

  • code_verifier: クライアント側で生成される、暗号学的に安全なランダム文字列 (RFC 7636で最小長43文字、最大長128文字と規定)。アクセストークンリクエスト時に使用される。
  • code_challenge: code_verifierから派生する値。認可リクエスト時に認可サーバーに送信される。
    • S256方式: code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))
    • plain方式: code_challenge = code_verifier (RFC 7636でSHALL NOT be usedと規定されており、非推奨)

PKCEを用いた認可コードフローは以下のステップで進行する。

  1. クライアントはセッションごとに一意のcode_verifierを生成する。
  2. クライアントはcode_verifierからcode_challengeを生成する (通常S256方式)。
  3. クライアントは認可リクエストを認可サーバーに送信する。この際、code_challengecode_challenge_method (例: S256) を含める。
  4. 認可サーバーはユーザーの認証と認可プロセスを経て、認可コードをクライアントのリダイレクトURIに送る。このとき、認可サーバーは受信したcode_challengeを保存しておく。
  5. クライアントは認可コードを受け取り、アクセストークンリクエストを認可サーバーのトークンエンドポイントに送信する。この際、初回生成したcode_verifier を含める。
  6. 認可サーバーは、受信したcode_verifierからcode_challengeを再計算し、保存していたcode_challengeと比較する。
  7. 検証が成功した場合のみ、認可サーバーはアクセストークンを発行する。

シーケンスダイアグラム

sequenceDiagram
    participant User
    participant Client
    participant "AS as Authorization Server"
    participant "RS as Resource Server"

    Client ->> Client: code_verifierを生成
    Client ->> Client: code_challengeを計算 (S256)

    Client ->> AS: 認可リクエスト (client_id, response_type=code, redirect_uri, scope, state, code_challenge, code_challenge_method=S256)
    AS ->> User: ユーザー認証・認可を要求
    User ->> AS: 認証情報と認可を送信
    AS ->> AS: code_challengeを保存

    AS -->> Client: 認可コードをリダイレクト (redirect_uri?code=..., state=...)

    Client ->> AS: アクセストークンリクエスト (grant_type=authorization_code, code, redirect_uri, code_verifier)
    AS ->> AS: 受信したcode_verifierからcode_challengeを再計算
    AS ->> AS: 保存済みcode_challengeと比較検証
    AS -->> Client: 検証成功: アクセストークン発行 (access_token, token_type, expires_in)

    Client ->> RS: リソースリクエスト (Authorization: Bearer access_token)
    RS -->> Client: リソースを応答

HTTPリクエスト/レスポンスの構造例

PKCEはHTTP上で動作するため、主要なパラメータはURIクエリパラメータまたはPOSTボディとして送信される。

認可リクエスト (GET):

GET /authorize?
  client_id={client_id}&           // OAuth 2.0クライアントID:N:bits
  response_type=code&             // 応答タイプ 'code':N:bits
  redirect_uri={redirect_uri}&    // リダイレクト先URI:N:bits
  scope={scope}&                  // 要求スコープ:N:bits
  state={state}&                  // CSRF対策用状態値:N:bits
  code_challenge={code_challenge}&// code_verifierの派生値:N:bits
  code_challenge_method=S256      // code_challenge生成方法 'S256':N:bits
 HTTP/1.1

認可レスポンス (HTTP 302 リダイレクト):

HTTP/1.1 302 Found
Location: {redirect_uri}?
  code={authorization_code}&    // 認可コード:N:bits
  state={state}                 // CSRF対策用状態値:N:bits

アクセストークンリクエスト (POST):

POST /token HTTP/1.1
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code& // グラントタイプ 'authorization_code':N:bits
code={authorization_code}&     // 受領した認可コード:N:bits
redirect_uri={redirect_uri}&   // 認可リクエスト時と同じURI:N:bits
code_verifier={code_verifier}  // 初回生成したcode_verifier:N:bits

アクセストークンレスポンス (JSON):

HTTP/1.1 200 OK
Content-Type: application/json

{
  "access_token": "{access_token}", // アクセストークン:N:bits
  "token_type": "Bearer",           // トークンタイプ 'Bearer':N:bits
  "expires_in": 3600,               // 有効期限 (秒):N:bits
  "refresh_token": "{refresh_token}",// リフレッシュトークン (オプション):N:bits
  "scope": "{scope}"                // 適用スコープ (オプション):N:bits
}

相互運用

PKCEはRFC 7636で定義され、OAuth 2.0 Core (RFC 6749) に追加されるセキュリティ拡張である。特にネイティブアプリ向けのOAuth 2.0 (RFC 8252) では、Public Clientにおける認可コードフローとPKCEの組み合わせが強く推奨されている。

既存プロトコルとの比較: – OAuth 2.0 Implicit Grant vs PKCE付きAuthorization Code Grant: – Implicit Grant: アクセストークンをブラウザのリダイレクトURIフラグメントで直接返す。 – 脆弱性: URLフラグメントはブラウザ履歴、中間者攻撃、クロスサイトスクリプティング (XSS) などにより漏洩するリスクがある。クライアント認証を行わない。 – PKCE付きAuthorization Code Grant: 認可コードを発行し、アクセストークンは認可サーバーとクライアント間のセキュアなバックチャネル (通常TLS) で交換される。 – code_verifierにより、認可コードを横取りされても、正規のクライアント以外はアクセストークンを取得できない。クライアント認証なしで、認可コード横取り攻撃に対する耐性を持つ。

OAuth 2.0 Security Best Current Practiceでは、Implicit Grantフローの利用は非推奨とされ、Public ClientにおいてはPKCE付き認可コードフローへの移行が推奨されている。

セキュリティ

PKCEは、Public ClientにおけるOAuth 2.0フローのセキュリティを大幅に向上させるが、考慮すべき点は存在する。

  • 認可コード横取り攻撃 (Authorization Code Interception Attack):
    • 攻撃者が正規のクライアントのふりをして認可コードを取得した場合でも、code_verifierを知らないため、アクセストークンと交換することができない。これはPKCEの主要な目的であり、効果的に緩和される。
  • ダウングレード攻撃:
    • 認可サーバーがcode_challenge_method=plainの利用を許容すると、攻撃者はS256対応クライアントに対しplainへのダウングレードを試みる可能性がある。この場合、code_challengecode_verifierと同一となり、セキュリティが低下する。
    • 緩和: 認可サーバーはRFC 7636の規定に従い、plainメソッドの使用を拒否し、S256を必須とすべきである。
  • リプレイ攻撃:
    • PKCEは認可コードが一度しか使用できない (One-Time Use) ことと、code_verifierの検証により、認可コード自体のリプレイを限定的にする。
    • アクセストークンはBearerトークンであるため、漏洩した場合はリプレイされる可能性がある。PKCEはアクセストークンの取得段階のセキュリティを強化するものであり、アクセストークン自体の保護は別途、TLSの利用、短い有効期限、リフレッシュトークンの安全な運用といった対策が必要である。
  • キー更新 (Key Rotation):
    • PKCEのcode_verifierはセッションごとに使い捨てのランダム値であり、厳密な意味でのキー更新の概念は直接適用されない。新しい認可フロー開始ごとに、新しいcode_verifierが生成されるため、これが実質的な「キー更新」として機能する。
  • 0-RTTの再送リスク:
    • PKCEはHTTP/TLS上で動作する。TLS 1.3の0-RTT機能は、初回ハンドシェイクをスキップしてアプリケーションデータを送信できるため、パフォーマンス向上に寄与する。しかし、アクセストークンリクエスト (POST) は冪等ではないため、0-RTTで送信された場合にネットワーク要因で再送されると、意図せず複数回処理されてしまう「リプレイ攻撃」の懸念が生じる。
    • 緩和: 認可サーバーは、0-RTTで受信したPOSTリクエストに対して厳格なリプレイ検出メカニズムを実装する必要がある。これにより、同じリクエストが複数回処理されることを防ぐ。

実装メモ

  • code_verifier生成: 暗号学的に安全な乱数ジェネレーター (例: crypto.getRandomValues() in Web APIs, java.security.SecureRandom in Java) を使用し、RFC 7636に準拠した長さ (43〜128文字) のバイト列を生成し、BASE64URLエンコードする。
  • code_challenge_method: 認可サーバーはS256を必須とし、クライアントもS256を優先して使用すべきである。plainメソッドのサポートは避けるべき。
  • code_verifierの安全な保存: クライアントは認可リクエストからアクセストークンリクエストまでの間、code_verifierを安全に保持する必要がある。モバイルアプリでは安全なストレージ、SPAではセッションストレージ(ただしXSS脆弱性に注意)またはWeb Worker内のメモリ空間などが検討される。
  • Redirect URIの厳格な検証: 認可サーバーは、登録されたredirect_uriとクライアントから提示されたredirect_uriを厳格に比較検証する必要がある。
  • MTU/Path MTU: HTTPリクエストはTCP/IP上で動作するため、MTU (Maximum Transmission Unit) はペイロードサイズに影響を与える。PKCEパラメータ自体は通常MTUを超えるほど大きくないが、URIクエリパラメータやPOSTボディのデータが増加すると、IPフラグメンテーションのリスクやTCPの輻輳制御に影響を与え、ネットワーク効率が低下する可能性がある。HTTPクライアント/サーバーの実装において、これらの下位レイヤーの挙動を理解することは、トラブルシューティングやパフォーマンスチューニングに役立つ。
  • HOL blocking回避/キュー制御/優先度: PKCEはアプリケーション層のセキュリティメカニズムであり、直接的な関連は薄いが、その基盤となるHTTP/2やHTTP/3といったプロトコル層では、Head-of-Line blocking回避、キュー制御、リクエスト優先度付けが導入されている。認可サーバーやAPIゲートウェイの実装においては、高負荷時でも安定した認可処理を提供するため、これらの機能活用によるリソース最適化と低レイテンシ化が重要となる。

まとめ

PKCE (RFC 7636) は、Public ClientにおけるOAuth 2.0認可コードフローのセキュリティを大幅に向上させる不可欠なメカニズムである。code_verifiercode_challengeの導入により、認可コード横取り攻撃を効果的に緩和し、クライアントシークレットを安全に保持できない環境での安全なOAuth 2.0運用を可能にする。Implicit Grantフローの非推奨化に伴い、PKCE付き認可コードフローが現在の標準的なセキュリティプラクティスとして推奨される。厳格な実装と継続的なセキュリティ考慮が、その効果を最大化するために必須である。

ライセンス:本記事のテキスト/コードは特記なき限り CC BY 4.0 です。引用の際は出典URL(本ページ)を明記してください。
利用ポリシー もご参照ください。

コメント

タイトルとURLをコピーしました