PKCEコード検証子とチャレンジの生成

JavaScriptのWeb Crypto APIを使用して暗号学的に安全なPKCE code_verifierとcode_challengeを生成するステップバイステップガイド。

PKCE

詳細な説明

PKCEコード検証子とチャレンジの生成

PKCE拡張には2つの値が必要です:code_verifier(ランダムな秘密)とcode_challenge(検証子の派生ハッシュ)。検証子はクライアントが保持し、チャレンジは認可リクエスト時に認可サーバーに送信されます。

要件(RFC 7636)

パラメータ 要件
code_verifier [A-Z] [a-z] [0-9] - . _ ~から43-128文字
code_challenge メソッドがS256の場合、BASE64URL(SHA256(code_verifier))
code_challenge_method S256(推奨)またはplain(非推奨)

JavaScript実装(Web Crypto API)

// ステップ1: ランダムなcode_verifierを生成
function generateCodeVerifier() {
  const array = new Uint8Array(32);
  crypto.getRandomValues(array);
  return base64url(array);
}

// ステップ2: code_challengeを導出
async function generateCodeChallenge(verifier) {
  const encoder = new TextEncoder();
  const data = encoder.encode(verifier);
  const digest = await crypto.subtle.digest("SHA-256", data);
  return base64url(new Uint8Array(digest));
}

// ヘルパー: Base64URLエンコーディング(パディングなし)
function base64url(buffer) {
  let str = "";
  for (const byte of buffer) {
    str += String.fromCharCode(byte);
  }
  return btoa(str)
    .replace(/\+/g, "-")
    .replace(/\//g, "_")
    .replace(/=+$/, "");
}

// 使用方法
const verifier = generateCodeVerifier();
const challenge = await generateCodeChallenge(verifier);
console.log("code_verifier:", verifier);
console.log("code_challenge:", challenge);

Python実装

import os
import hashlib
import base64

code_verifier = base64.urlsafe_b64encode(os.urandom(32)).rstrip(b"=").decode()
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode()

なぜPlainではなくS256か?

plainでは、code_challengeはcode_verifierと等しくなります。つまり、チャレンジ(フロントチャネルリダイレクトで送信)を傍受した攻撃者は即座に検証子を知ることができます。S256ではチャレンジはSHA-256ハッシュであり、攻撃者はそれを逆算して検証子を取得できません。常にS256を使用してください。

ユースケース

OAuth 2.0認可フローを開始する前に、JavaScript SPA、React Nativeモバイルアプリ、またはNode.js CLIツールでPKCE値を生成する。code_verifierはローカルに保存(例:sessionStorage)し、code_challengeは認可リクエストで送信します。

試してみる — OAuth 2.0 Flow Visualizer

フルツールを開く