OIDC / OAuth 2.0

PKCEの仕組み

セキュリティ

認可コード横取り攻撃を防ぐProof Key for Code Exchange (PKCE) の仕組みを詳しく解説します。

PKCEが解決する問題

PKCE(Proof Key for Code Exchange、「ピクシー」と読む)は、認可コード横取り攻撃を防ぐためのセキュリティ拡張です(RFC 7636)。

認可コードフローでは、認可サーバーからクライアントに認可コードがリダイレクトで渡されます。 この時、悪意のあるアプリがリダイレクトを傍受して認可コードを盗み、 正規のクライアントになりすましてトークンを取得する攻撃が可能です。

認可コード横取り攻撃

特にモバイルアプリやSPAなどのパブリッククライアントでは、 client_secret を安全に保持できないため、この攻撃が深刻です。

1

正規のアプリがブラウザで認可リクエストを送信

2

ユーザーが認可サーバーで認証・同意する

3

認可サーバーがredirect_uriにリダイレクト(認可コード付き)

!

悪意のあるアプリがカスタムURLスキームを登録しており、 リダイレクトを横取りして認可コードを取得

5

悪意のあるアプリが認可コードでトークンを取得(client_secretなしで)

code_verifier と code_challenge

code_verifier

クライアントが生成する暗号的にランダムな文字列。 43~128文字の英数字および記号(-._~)で構成される。

dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

code_challenge

code_verifierのSHA-256ハッシュをBase64URLエンコードした値。 認可リクエストに含めて送信する。

E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM
S256メソッド

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が漏洩するため、 使用してはいけません。

PKCEを使ったフロー
クライアント                    認可サーバー
    |                                |
    |  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_secretPKCE
SPA保持不可必須
モバイルアプリ保持不可必須
デスクトップアプリ保持不可必須
サーバーサイドアプリ保持可推奨

OAuth 2.1 のドラフトでは、すべてのクライアントタイプでPKCEが必須とされています。 サーバーサイドアプリであっても、PKCEを使うことが強く推奨されます。

次のステップ

次は トークンの安全な保存方法 で、取得したトークンをどこに保存すべきかを学びましょう。