← 中文资源

简体中文版本

给 OAuth 公共客户端流程加 PKCE

为每次请求生成 code verifier、把其挑战值放进授权请求、并在换码时证明持有 verifier。

阅读英文原文

目标

授权码流程对公共客户端有个结构性弱点:code 要经过其他应用可截获的重定向机制传递。PKCE 把 code 焊死在攻击者从未拿到的东西上——每次请求随机生成的 verifier,其哈希随请求走、其原值在换码时证明。被截获的 code 于是成了一张贼无法完成的交易回执。

步骤要点

  1. 每次授权尝试生成 32 字节 base64url 的 codeVerifier,并计算 SHA-256 的 codeChallenge。
  2. 授权请求携带 code_challenge 与 code_challenge_method=S256,授权服务器把签发的 code 绑定到该挑战值。
  3. 换码请求带 authorization_code + code_verifier,服务器哈希比对,无 verifier 的截获 code 必然失败。
  4. 策略上只允许 S256:出现 plain 方法即拒绝,杜绝挑战值即答案的降级。
  5. 用错误 verifier 兑换同一 code 实测:应得 400 invalid_grant,证明赃码无效。

排错提示

服务器拒绝 S256 说明其 PKCE 支持需按客户端启用或版本过旧,先查元数据里的 code_challenge_methods_supported;只做 PKCE 不带 state 是混了两种攻击面,PKCE 防 code 被盗用、state 防 CSRF 并携带关联,缺一不可;verifier 只需活到换码为止,放内存随请求走,勿落持久存储被后续读取复用。

适用边界

PKCE 保护的是换码环节,救不了被攻陷的授权服务器或被恶意注册的重定向;机密客户端还应叠加客户端认证,PKCE 作为纵深防御依然值得。

代码示例

// Per attempt
const codeVerifier = crypto.randomBytes(32).toString("base64url");
const codeChallenge = crypto.createHash("sha256")
  .update(codeVerifier).digest("base64url");

// authorize?...code_challenge=...&code_challenge_method=S256
// token: code + code_verifier (the proof)