モバイル・SPA時代の必須教養:PKCEでOAuth 2.0の「認可コード横取り」を封じ込める
ネットワークの最前線でパケットを追いかけていると、「なぜこの設計にしたのか?」という問いが、システムの堅牢性を左右する決定的な分岐点になることを痛感します。
OAuth 2.0の認可コードフローにおいて、特にモバイルアプリやSPA(Single Page Application)といった「クライアントシークレットを隠し持てない環境」をどう守るか。かつては暗黙的フロー(Implicit Flow)が重宝されましたが、今やそれはセキュリティホールの代名詞です。そこで登場したのが PKCE (Proof Key for Code Exchange / RFC 7636) です。
今日は、RFC 7636の仕様を単なる暗記ではなく、パケットが網の上でどう「信頼」を証明しているのか、その泥臭い仕組みを紐解いていきましょう。
—
なぜPKCEが必要なのか?—「横取り」の脅威
従来の認可コードフローでは、認可サーバーからクライアントへ code が発行され、それを client_secret と引き換えにアクセストークンへ交換します。しかし、モバイルアプリやブラウザ環境では、client_secret をソースコードに埋め込むことは不可能です。
攻撃者は、OSのカスタムURIスキームを悪用し、正規アプリよりも先に code を横取り(Authorization Code Interception Attack)できてしまいます。client_secret がない環境では、攻撃者がその code を使ってトークンを要求すれば、認証は突破されてしまうのです。
PKCEは、この「信頼できないクライアント」に、一時的な鍵(Code Verifier)を生成させることで、「認可要求を出した本人しかトークンを得られない」という強力な制約を課します。
—
PKCEの通信フロー:パケットが語る「証明」の仕組み
PKCEの肝は、認可リクエスト時とトークン交換時の二段階で、「同じ送信元であること」を数学的に証明させる点にあります。
1. Code Verifier の生成
クライアントはランダムな文字列(code_verifier)を生成します。
2. Code Challenge の作成
code_verifier をSHA-256でハッシュ化し、Base64URLエンコードした code_challenge を用意します。
3. 認可リクエスト (Authorization Request)
認可エンドポイントへ以下のパラメータを付与して送信します。
code_challenge: 上記で作成した値code_challenge_method:S256(現在はこれ一択です)
4. トークンリクエスト (Token Request)
認可サーバーから届いた code を使う際、今度は生の code_verifier を付与します。認可サーバーは、受け取った code_verifier をハッシュ化し、先ほど保存しておいた code_challenge と一致するかを検証します。
—
実装の勘所:Pythonで見るCode Verifier生成
現場でよくあるミスは、ハッシュ値のエンコード形式です。必ずURLセーフなBase64エンコードを行ってください。
import hashlib
import base64
import os
# 1. 43〜128文字のランダムな文字列を生成 (Code Verifier)
code_verifier = base64.urlsafe_b64encode(os.urandom(32)).decode('utf-8').replace('=', '')
# 2. SHA-256でハッシュ化し、Base64URLエンコード (Code Challenge)
# ここで「=」パディングを除去するのがポイント
hashed = hashlib.sha256(code_verifier.encode('utf-8')).digest()
code_challenge = base64.urlsafe_b64encode(hashed).decode('utf-8').replace('=', '')
print(f"Code Verifier: {code_verifier}")
print(f"Code Challenge: {code_challenge}")
—
現場で役立つデバッグのTips
インフラエンジニアとしてAPIの結合テストを行う際、必ず行うべきチェックポイントを挙げます。
code_challenge_methodの強制: サーバーサイドの実装では、plainを許可せず、必ずS256を必須にするバリデーションを入れてください。plainは非推奨であり、中間者攻撃に対して無力です。- 通信の可視化: 開発中は
curlでの再現が必須です。以下のようにリクエストを投げ、サーバーがどう拒否するかを確認しましょう。
# トークン交換時のリクエスト例
curl -X POST https://auth.example.com/token \
-d "grant_type=authorization_code" \
-d "code=<認可コード>" \
-d "client_id=<クライアントID>" \
-d "code_verifier=<最初に生成した文字列>" # ここで検証が行われる
- ログ解析: トークンエンドポイントで「400 Bad Request」が返る場合、まず疑うべきは
code_verifierの文字コードやパディング処理の不一致です。ログにcode_challengeの不一致エラーが出ていないか確認してください。
—
最後に:ネットワークスペシャリストからの提言
PKCEを導入するということは、単に「仕様を埋める」ことではありません。「秘匿情報を扱えない環境」という制約を、計算量的な優位性でカバーするという高度なアーキテクチャの判断です。
APIの設計者として、エンドポイントのURLを美しく整理するだけでなく、その裏側を流れる認可のフローに「破れない証明」を組み込むこと。それこそが、プロフェッショナルなインフラエンジニアの仕事です。
SPAやモバイルアプリの認可基盤を設計する際は、まずはRFC 7636を枕元に置いてください。もしトラブルシューティングでパケットキャプチャが必要になったら、その時はまた、パケットの海でお会いしましょう。
コメント