コンテンツにスキップ

PKCE

PKCE (Proof Key for Code Exchange)

PKCE(RFC 7636)は、認可コードの傍受攻撃から保護するためのセキュリティ拡張です。特にクライアントシークレットを安全に保存できないパブリッククライアント(モバイルアプリやSPA)に必須です。

概要

なぜPKCEを使用するのか?

セキュリティ上の利点

  1. 認可コード傍受攻撃の防止

    • 悪意のあるアプリによるコード窃取を防止
    • コードインジェクション攻撃から保護
    • パブリッククライアントに必須
  2. モバイルアプリのセキュリティ

    • 同じカスタムURIスキームを持つ悪意のあるアプリから保護
    • アプリ間リダイレクトで動作
    • OAuth 2.1要件に準拠
  3. SPA(シングルページアプリケーション)のセキュリティ

    • ブラウザでのクライアントシークレット不要
    • コードを窃取するXSS攻撃から保護
    • OAuth 2.1必須要件の一部
  4. OAuth 2.1準拠

    • OAuth 2.1ではPKCEが必須
    • すべてのクライアント(コンフィデンシャル含む)に推奨
    • 将来を見据えたセキュリティ

ユースケース

  • モバイルアプリ(iOS、Android、React Native)
  • SPA(React、Vue、Angular)
  • デスクトップアプリ(Electron)
  • CLIツール
  • IoTデバイス
  • パブリッククライアント全般

PKCEの仕組み

フロー概要

  1. code_verifier生成: クライアントが暗号論的にランダムな文字列を生成(43-128文字)
  2. code_challenge計算: BASE64URL(SHA-256(code_verifier))
  3. 認可リクエスト: code_challengecode_challenge_method=S256を含める
  4. トークンリクエスト: 元のcode_verifierを含める
  5. 検証: サーバーがSHA-256(code_verifier) == code_challengeを検証

APIリファレンス

PKCEを使用した認可エンドポイント

GET/POST /authorize

パラメータ必須説明
code_challengeYes*code_verifierのBase64urlエンコードされたSHA-256ハッシュ
code_challenge_methodYes*S256(SHA-256)である必要がある
response_typeYescodeである必要がある
client_idYesクライアント識別子
redirect_uriYesコールバックURI
scopeYesリクエストするスコープ
state推奨CSRF保護

*パブリッククライアントでは必須、すべてのクライアントに推奨

PKCEを使用したトークンエンドポイント

POST /token

パラメータ必須説明
grant_typeYesauthorization_codeである必要がある
codeYes/authorizeからの認可コード
redirect_uriYes/authorizeと同じredirect_uri
client_idYesクライアント識別子
code_verifierYes*元のランダム文字列(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 - サポートなし(安全でないため非推奨)

セキュリティ考慮事項

  1. code_verifierのセキュリティ

    • 暗号論的に安全な乱数生成器を使用
    • 安全に保存(sessionStorage、セキュアエンクレーブ)
    • code_verifierをログや公開しない
    • 使用後は削除
  2. パブリッククライアント

    • モバイルアプリ、SPA、デスクトップアプリ、CLIツールでPKCE必須
  3. コンフィデンシャルクライアント

    • すべてのクライアントに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方式必須

参考資料