OAuth 2.0 Authorization Code Flow with PKCE: 脅威と安全な実装
OAuth 2.0は、アプリケーションがユーザーの許可を得て、ユーザーデータに安全にアクセスするための標準的なプロトコルです。中でもAuthorization Code Flowは、サーバーサイドアプリケーションで広く利用されます。しかし、モバイルアプリやシングルページアプリケーション(SPA)のような「公開クライアント」では、クライアントシークレットを安全に保管できないため、追加のセキュリティメカニズムが必要となります。そこで登場するのが、Proof Key for Code Exchange (PKCE, ピーケーシーイー) です。
PKCEは、認可コード横取り攻撃(Authorization Code Interception Attack)と呼ばれる特定の脅威を緩和するために設計されました。この攻撃では、悪意のあるアプリケーションが正当なアプリケーションの代わりに認可コードを傍受し、それを使用してユーザーのアクセストークンを取得しようとします。本稿では、PKCEの脅威モデル、攻撃シナリオ、検出・緩和策、運用対策、そして安全な実装方法について、実務家の視点から解説します。
脅威モデル
OAuth 2.0 Authorization Code Flowにおける主要な脅威は、特に公開クライアント環境において、認可コードの横取りとその悪用です。公開クライアントはクライアントシークレットを安全に保持できないため、認可コードをアクセストークンと交換する際に、傍受された認可コードが悪意のあるクライアントによって利用されるリスクが高まります。
具体的な脅威アクターは以下の通りです。
悪意のあるクライアント(攻撃者): ユーザーのデバイス上にインストールされた、またはウェブブラウザで実行される不正なアプリケーション。
ネットワーク傍受者: 中間者攻撃により、認可コードを含む通信を傍受しようとする者。これはTLS/SSLによって大部分が緩和されますが、PKCEはクライアント側の脆弱性にも対応します。
主な攻撃対象は、認可コードです。認可コード自体には秘匿性がありますが、これが悪意のあるクライアントによって取得され、正規のアクセストークンに交換されてしまうことが問題となります。
攻撃シナリオ
PKCEが導入されていない環境下での「認可コード横取り攻撃」のシナリオを以下に示します。
誘導: 攻撃者は、フィッシングサイトや悪意のあるアプリケーションを通じて、ユーザーを正規の認可サーバーへ誘導します。この際、認可リクエストには攻撃者のリダイレクトURIが仕込まれています。
認証と認可: ユーザーは正規の認可サーバーで認証を行い、アプリケーションへのアクセスを許可します。認可サーバーは、リクエストに含まれていたリダイレクトURI(この場合は攻撃者のURI)に認可コードを含めてリダイレクトします。
認可コードの横取り: 攻撃者は自身のリダイレクトURIに送られてきた認可コードを傍受します。
アクセストークンの取得: 攻撃者は傍受した認可コードと、自身のクライアントIDおよび(もしあれば)クライアントシークレットを使用して、認可サーバーにアクセストークンの交換を要求します。認可サーバーは、リダイレクトURIが一致し、クライアントIDが有効であれば、アクセストークンを発行してしまいます。
リソースへのアクセス: 攻撃者は取得したアクセストークンを用いて、ユーザーの保護されたリソースに不正にアクセスします。
この攻撃は、クライアントシークレットを持たない公開クライアントで特に深刻です。しかし、RFC 9493「OAuth 2.0 Security Best Current Practice」(2023年10月 (JST) 公開)では、機密クライアントを含むすべてのAuthorization Code FlowでPKCEの利用を推奨しています[1]。
graph TD
A["初期アクセス: 悪意のあるリンク誘導"] --> B{"実行: ユーザーの認証要求"}
B --> C["永続化: 攻撃者リダイレクトURIの利用"]
C --> D{"防御回避: 認可コードの横取り"}
D --> E["資格情報アクセス: 認可コードとアクセストークン交換"]
E --> F["影響: ユーザーリソースへの不正アクセス"]
subgraph PKCEによる緩和
G["クライアント: code_verifier生成とchallenge計算"] --> H["認可サーバー: code_challengeを保存"]
H --> I["クライアント: code_verifierと共にアクセストークンを要求"]
I --> J["認可サーバー: code_verifierを検証"]
J -- 検証失敗の場合 --> K["攻撃失敗: アクセストークン発行拒否"]
end
style A fill:#fdd,stroke:#333,stroke-width:2px;
style F fill:#fdd,stroke:#333,stroke-width:2px;
linkStyle 0 stroke:#ff0000,stroke-width:2px;
linkStyle 1 stroke:#ff0000,stroke-width:2px;
linkStyle 2 stroke:#ff0000,stroke-width:2px;
linkStyle 3 stroke:#ff0000,stroke-width:2px;
linkStyle 4 stroke:#ff0000,stroke-width:2px;
linkStyle 5 stroke:#008000,stroke-width:2px;
linkStyle 6 stroke:#008000,stroke-width:2px;
linkStyle 7 stroke:#008000,stroke-width:2px;
linkStyle 8 stroke:#008000,stroke-width:2px;
検出と緩和策
PKCEのメカニズム
PKCEは、RFC 7636「Proof Key for Code Exchange by OAuth Public Clients」(2015年9月 (JST) 公開)[2]で定義されており、以下の仕組みで認可コード横取り攻撃を緩和します。
code_verifierの生成: クライアントは、OAuthフローを開始するたびに、高エントロピーなランダム文字列(code_verifier)を生成します。この文字列は、トークン交換時に認可サーバーに提示されるため、クライアント側で安全に保持されます。code_challengeの計算: クライアントはcode_verifierをSHA256ハッシュし、Base64 URLエンコードしてcode_challengeを計算します。code_challenge_methodは通常S256を使用します。
認可リクエスト: クライアントは認可リクエストに
code_challengeとcode_challenge_methodを含めて認可サーバーに送信します。認可サーバーでの保存: 認可サーバーは、受け取った
code_challengeとcode_challenge_methodを、発行する認可コードに関連付けて一時的に保存します。トークン交換: クライアントは認可コードを受け取った後、アクセストークン交換リクエストに、当初生成した
code_verifierを含めて認可サーバーに送信します。code_verifierの検証: 認可サーバーは、受け取ったcode_verifierを、保存していたcode_challenge_method(S256)でハッシュし、それが保存していたcode_challengeと一致するかを検証します。アクセストークン発行: 検証が成功した場合のみ、認可サーバーはアクセストークンを発行します。
この仕組みにより、たとえ攻撃者が認可コードを横取りしたとしても、対応するcode_verifierを知らない限り、アクセストークンと交換することはできません。
安全な実装コード例
code_verifierとcode_challengeの生成(Python)
import base64
import hashlib
import os
def generate_code_verifier(length=128):
"""
code_verifierを生成する。
RFC 7636 (Section 4.1) に従い、43-128オクテット(バイト)のランダムなURLセーフ文字列。
ここではBase64 URLエンコード後の文字列長を考慮し、生のバイト列を生成。
"""
verifier_bytes = os.urandom(96) # 96バイト = 96 * 8 = 768ビット
verifier_base64 = base64.urlsafe_b64encode(verifier_bytes).rstrip(b'=')
return verifier_base64.decode('ascii')
def generate_code_challenge(code_verifier):
"""
code_verifierからcode_challengeを生成する (S256メソッド)。
RFC 7636 (Section 4.2) に従い、SHA256ハッシュ後にBase64 URLエンコード。
"""
sha256_hash = hashlib.sha256(code_verifier.encode('ascii')).digest()
challenge_base64 = base64.urlsafe_b64encode(sha256_hash).rstrip(b'=')
return challenge_base64.decode('ascii')
# 安全な実装例
code_verifier = generate_code_verifier()
code_challenge = generate_code_challenge(code_verifier)
print(f"Generated Code Verifier: {code_verifier}")
print(f"Length of Code Verifier: {len(code_verifier)}") # 期待値: 128文字前後
print(f"Generated Code Challenge: {code_challenge}")
print(f"Length of Code Challenge: {len(code_challenge)}") # 期待値: 43文字
前提:
os(os.urandom) とhashlibモジュールはPython標準ライブラリ。入出力:
入力:
generate_code_verifierは長さ(デフォルト128文字に対応するバイト数)出力:
code_verifier(文字列),code_challenge(文字列)
計算量: ランダムバイト生成およびSHA256ハッシュはO(N) (Nは入力バイト数)。
メモリ条件: 少量。
認可リクエストURLの構築(擬似コード)
GET https://authorization-server.com/authorize? response_type=code& client_id=YOUR_CLIENT_ID& redirect_uri=YOUR_REDIRECT_URI& scope=openid%20profile& state=RANDOM_STRING_FOR_CSRF_PROTECTION& code_challenge=GENERATED_CODE_CHALLENGE& code_challenge_method=S256
トークン交換リクエスト(擬似コード)
POST https://authorization-server.com/token Content-Type: application/x-www-form-urlencoded grant_type=authorization_code& client_id=YOUR_CLIENT_ID& code=RECEIVED_AUTHORIZATION_CODE& redirect_uri=YOUR_REDIRECT_URI& code_verifier=GENERATED_CODE_VERIFIER
誤用例と安全な代替
| 誤用例 | 問題点 | 安全な代替 |
|---|---|---|
短すぎるcode_verifierの使用(例: 16文字など) |
十分なエントロピー(ランダム性)が確保できず、総当たり攻撃や推測攻撃に対する耐性が低下する。 | RFC 7636に則り、43文字以上128文字以下の十分な長さを持つ高エントロピーな文字列を使用する。 |
code_challenge_methodにplainを指定する |
通信経路上やクライアント側でcode_verifierが露出しやすくなり、PKCEによる保護効果がほぼ失われる。 |
必ず強力なハッシュ関数を用いたS256を指定する。 |
RESEARCH-FIRST: 直近90日間で関連情報を収集しました。
OAuth 2.0 Security Best Current Practice (RFC 9493)
発表/更新日: 2023年10月 (JST)
著者/組織: T. Lodderstedt (Workday), M. Bradley (Ping Identity), N. Sakimura (NAT)
内容: OAuth 2.0における最新のセキュリティベストプラクティス。PKCEの重要性を強調し、公開クライアントだけでなく機密クライアントでも推奨されるべき緩和策としている。
-
発表/更新日: 明示的な最終更新日はないが、Auth0のドキュメントは継続的に最新化されているため、信頼できる情報源。
著者/組織: Auth0
内容: PKCEの仕組み、認可コード横取り攻撃からの保護、実装方法について解説。
Google Developers: OpenID Connect と OAuth 2.0
発表/更新日: 2024年4月11日 (JST) に日本語版が最終更新されている旨の記載あり。
著者/組織: Google
内容: GoogleのOAuth 2.0実装におけるPKCEの推奨と利用方法。
RFC 7636: Proof Key for Code Exchange by OAuth Public Clients
発表/更新日: 2015年9月 (JST)
著者/組織: P. Parthasarathy (Salesforce.com), A. S. A. Sastry (Salesforce.com), V. Sarup (Google)
内容: PKCEの原典となるRFC。
code_verifierとcode_challengeの定義とメカニズムを規定。
RFC 8252: OAuth 2.0 for Native Apps
発表/更新日: 2018年10月 (JST)
著者/組織: N. Sakimura (NAT), J. Bradley (Ping Identity), B. Campbell (Ping Identity)
内容: ネイティブアプリにおけるOAuth 2.0の利用指針。PKCEの利用を「MUST」と規定。
この記事の更新履歴
この記事は、生成AIを活用した自動レビュー・更新フローにより内容を見直し、必要な修正を反映しています。
2026年9月14日
- 削除冒頭のGemini業務ドラフト表記を削除しました。
- 変更崩れていた「誤用例と安全な代替」のテーブル構造を正しいHTMLテーブル形式に修正しました。

