PKCE(Proof Key for Code Exchange、「ピクシー」と読む)は、認可コード横取り攻撃を防ぐためのセキュリティ拡張です(RFC 7636)。
認可コードフローでは、認可サーバーからクライアントに認可コードがリダイレクトで渡されます。 この時、悪意のあるアプリがリダイレクトを傍受して認可コードを盗み、 正規のクライアントになりすましてトークンを取得する攻撃が可能です。
特にモバイルアプリやSPAなどのパブリッククライアントでは、 client_secret を安全に保持できないため、この攻撃が深刻です。
正規のアプリがブラウザで認可リクエストを送信
ユーザーが認可サーバーで認証・同意する
認可サーバーがredirect_uriにリダイレクト(認可コード付き)
悪意のあるアプリがカスタムURLスキームを登録しており、 リダイレクトを横取りして認可コードを取得
悪意のあるアプリが認可コードでトークンを取得(client_secretなしで)
code_verifier
クライアントが生成する暗号的にランダムな文字列。 43~128文字の英数字および記号(-._~)で構成される。
code_challenge
code_verifierのSHA-256ハッシュをBase64URLエンコードした値。 認可リクエストに含めて送信する。
code_challengeの生成には S256 メソッドが推奨されます。 plain メソッドも仕様上は存在しますが、セキュリティ上の理由から S256 を使うべきです。
code_challenge = BASE64URL(SHA256(code_verifier))
// 具体的な実装例
async function generateCodeChallenge(codeVerifier: string) {
const encoder = new TextEncoder();
const data = encoder.encode(codeVerifier);
const digest = await crypto.subtle.digest("SHA-256", data);
const base64 = btoa(String.fromCharCode(...new Uint8Array(digest)));
return base64
.replace(/\+/g, "-")
.replace(/\//g, "_")
.replace(/=+$/, "");
}plain メソッド(code_challenge = code_verifier)は、 通信経路上で傍受された場合にcode_verifierが漏洩するため、 使用してはいけません。
クライアント 認可サーバー
| |
| 1. code_verifier を生成 |
| 2. code_challenge を計算 |
| |
|--- 認可リクエスト --------------->|
| code_challenge=E9Me... |
| code_challenge_method=S256 |
| |
|<-- 認可コード -------------------|
| code=SplxlOB... |
| |
|--- トークンリクエスト ----------->|
| code=SplxlOB... |
| code_verifier=dBjf... |
| |
| 認可サーバーが検証: |
| SHA256(code_verifier) |
| == 保存していたcode_challenge? |
| |
|<-- トークンレスポンス ------------|
| access_token=... |
| id_token=... |攻撃者が認可コードを横取りしても、code_verifierを知らないため トークンリクエストが成功しません。 code_challengeからcode_verifierを逆算することは SHA-256の一方向性により不可能です。
| クライアントタイプ | client_secret | PKCE |
|---|---|---|
| SPA | 保持不可 | 必須 |
| モバイルアプリ | 保持不可 | 必須 |
| デスクトップアプリ | 保持不可 | 必須 |
| サーバーサイドアプリ | 保持可 | 推奨 |
OAuth 2.1 のドラフトでは、すべてのクライアントタイプでPKCEが必須とされています。 サーバーサイドアプリであっても、PKCEを使うことが強く推奨されます。
次のステップ
次は トークンの安全な保存方法 で、取得したトークンをどこに保存すべきかを学びましょう。