PKCE
PKCE (Proof Key for Code Exchange)
PKCE(RFC 7636)は、認可コードの傍受攻撃から保護するためのセキュリティ拡張です。特にクライアントシークレットを安全に保存できないパブリッククライアント(モバイルアプリやSPA)に必須です。
概要
- RFC: RFC 7636 - Proof Key for Code Exchange
- ステータス: 完全実装
- 方式:
S256(SHA-256)- 推奨かつ必須 - 対応フロー: 認可コードフロー
なぜPKCEを使用するのか?
セキュリティ上の利点
-
認可コード傍受攻撃の防止
- 悪意のあるアプリによるコード窃取を防止
- コードインジェクション攻撃から保護
- パブリッククライアントに必須
-
モバイルアプリのセキュリティ
- 同じカスタムURIスキームを持つ悪意のあるアプリから保護
- アプリ間リダイレクトで動作
- OAuth 2.1要件に準拠
-
SPA(シングルページアプリケーション)のセキュリティ
- ブラウザでのクライアントシークレット不要
- コードを窃取するXSS攻撃から保護
- OAuth 2.1必須要件の一部
-
OAuth 2.1準拠
- OAuth 2.1ではPKCEが必須
- すべてのクライアント(コンフィデンシャル含む)に推奨
- 将来を見据えたセキュリティ
ユースケース
- モバイルアプリ(iOS、Android、React Native)
- SPA(React、Vue、Angular)
- デスクトップアプリ(Electron)
- CLIツール
- IoTデバイス
- パブリッククライアント全般
PKCEの仕組み
フロー概要
- code_verifier生成: クライアントが暗号論的にランダムな文字列を生成(43-128文字)
- code_challenge計算:
BASE64URL(SHA-256(code_verifier)) - 認可リクエスト:
code_challengeとcode_challenge_method=S256を含める - トークンリクエスト: 元の
code_verifierを含める - 検証: サーバーが
SHA-256(code_verifier) == code_challengeを検証
APIリファレンス
PKCEを使用した認可エンドポイント
GET/POST /authorize
| パラメータ | 必須 | 説明 |
|---|---|---|
code_challenge | Yes* | code_verifierのBase64urlエンコードされたSHA-256ハッシュ |
code_challenge_method | Yes* | S256(SHA-256)である必要がある |
response_type | Yes | codeである必要がある |
client_id | Yes | クライアント識別子 |
redirect_uri | Yes | コールバックURI |
scope | Yes | リクエストするスコープ |
state | 推奨 | CSRF保護 |
*パブリッククライアントでは必須、すべてのクライアントに推奨
PKCEを使用したトークンエンドポイント
POST /token
| パラメータ | 必須 | 説明 |
|---|---|---|
grant_type | Yes | authorization_codeである必要がある |
code | Yes | /authorizeからの認可コード |
redirect_uri | Yes | /authorizeと同じredirect_uri |
client_id | Yes | クライアント識別子 |
code_verifier | Yes* | 元のランダム文字列(43-128文字) |
*認可リクエストでcode_challengeが提供された場合は必須
使用例
JavaScript/TypeScript(ブラウザ)
// Step 1: code_verifierを生成function generateCodeVerifier(): string { const array = new Uint8Array(32); crypto.getRandomValues(array); return base64urlEncode(array);}
function base64urlEncode(buffer: Uint8Array): string { const base64 = btoa(String.fromCharCode(...buffer)); return base64 .replace(/\+/g, '-') .replace(/\//g, '_') .replace(/=/g, '');}
// Step 2: code_challengeを計算async function generateCodeChallenge(verifier: string): Promise<string> { const encoder = new TextEncoder(); const data = encoder.encode(verifier); const hashBuffer = await crypto.subtle.digest('SHA-256', data); return base64urlEncode(new Uint8Array(hashBuffer));}
// Step 3: 認可フローを開始async function startAuthorization() { const codeVerifier = generateCodeVerifier(); const codeChallenge = await generateCodeChallenge(codeVerifier);
// 後で使用するためにcode_verifierを保存 sessionStorage.setItem('code_verifier', codeVerifier);
// 認可URLを構築 const authUrl = new URL('https://auth.example.com/authorize'); authUrl.searchParams.set('response_type', 'code'); authUrl.searchParams.set('client_id', 'my_client_id'); authUrl.searchParams.set('redirect_uri', 'https://myapp.example.com/callback'); authUrl.searchParams.set('scope', 'openid profile email'); authUrl.searchParams.set('state', generateRandomState()); authUrl.searchParams.set('code_challenge', codeChallenge); authUrl.searchParams.set('code_challenge_method', 'S256');
window.location.href = authUrl.toString();}
// Step 4: コールバックを処理してトークンを取得async function handleCallback() { const params = new URLSearchParams(window.location.search); const code = params.get('code'); const codeVerifier = sessionStorage.getItem('code_verifier');
const response = await fetch('https://auth.example.com/token', { method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', }, body: new URLSearchParams({ grant_type: 'authorization_code', code: code!, redirect_uri: 'https://myapp.example.com/callback', client_id: 'my_client_id', code_verifier: codeVerifier!, }), });
const tokens = await response.json(); // tokens.access_token, tokens.id_token, etc.}実装詳細
code_verifier要件
- 長さ: 43-128文字
- 文字:
[A-Z],[a-z],[0-9],-,.,_,~ - エントロピー: 最低256ビット推奨
- 生成: 暗号論的に安全な乱数生成器を使用
チャレンジ方式
Authrimがサポート:
- S256(SHA-256)- 推奨かつデフォルト
plain- サポートなし(安全でないため非推奨)
セキュリティ考慮事項
-
code_verifierのセキュリティ
- 暗号論的に安全な乱数生成器を使用
- 安全に保存(sessionStorage、セキュアエンクレーブ)
- code_verifierをログや公開しない
- 使用後は削除
-
パブリッククライアント
- モバイルアプリ、SPA、デスクトップアプリ、CLIツールでPKCE必須
-
コンフィデンシャルクライアント
- すべてのクライアントにPKCE推奨(多層防御)
エラーレスポンス
{ "error": "invalid_request", "error_description": "code_challengeが提供された場合、code_verifierは必須です"}{ "error": "invalid_grant", "error_description": "code_verifierがcode_challengeと一致しません"}準拠
- OAuth 2.1: すべての認可コードフローでPKCE必須
- FAPI 2.0: PKCE必須、S256方式必須