OAuth 2.0 PKCE授权报错:code_verifier无效(400错误)
排查Etsy OAuth 2.0的code_verifier无效问题
核心错误分析
代码存在两处致命逻辑错误,直接导致code_verifier is invalid报错:
- code_verifier与code_challenge定义混淆
- OAuth 2.0 PKCE规范要求:
code_verifier是原始随机字符串(经base64url编码),code_challenge是对该code_verifier做SHA256哈希后再base64url编码的结果。 - 当前代码将base64url后的随机字符串误命名为
$code_challenge,同时把哈希后的结果作为请求参数的code_challenge,还错误地将这个命名错误的变量存入session,在获取token时当作code_verifier传递,完全颠倒了两者的关系。
- OAuth 2.0 PKCE规范要求:
- SHA256哈希生成方式错误
- PHP的
hash('sha256', $str)默认返回十六进制字符串,而非原始二进制哈希值。直接对该十六进制字符串做base64编码,会导致哈希结果不符合Etsy的校验规则。必须传递第三个参数true以获取原始二进制哈希。
- PHP的
修正后的代码
生成授权URL的函数
function GetOauthUrl(){ $_SESSION["etsy_state"] = getRadomString(128); // 生成32字节的密码学安全随机数据(符合PKCE规范的code_verifier长度要求) $randomBytes = random_bytes(32); // 对原始随机数据做base64url编码,得到合法的code_verifier $code_verifier = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($randomBytes)); // 用code_verifier生成code_challenge:先做SHA256二进制哈希,再base64url编码 $hashBinary = hash('sha256', $code_verifier, true); $code_challenge = str_replace(['+', '/', '='], ['-', '_', ''], base64_encode($hashBinary)); // 构造授权URL $url = "https://www.etsy.com/oauth/connect?response_type=code&client_id={$_SESSION['etsy_clientId']}&redirect_uri={$_SESSION['etsy_url']}" ."&scope=listings_r%20listings_w%20listings_d&state={$_SESSION['etsy_state']}&code_challenge={$code_challenge}&code_challenge_method=S256"; // 存储正确的code_verifier到session,用于后续获取token $_SESSION["etsy_code_verifier"] = $code_verifier; return $url; }
获取Token的函数
function GetOauthToken(){ $postfields = "grant_type=authorization_code&client_id=".$_SESSION["etsy_clientId"] ."&redirect Sitting单ur分享=$ and {am amodified的 Currentsubstituted with _ and = removed: ".$_SESSION['etsy_url'] ."&code=".$_SESSION["etsy_authCode"] ."&code_verifier=".$_SESSION["etsy_code_verifier"]; // 使用存储的正确code_verifier $url = "/v3/public/oauth/token"; return PostHttpRequest($url, array('Cache-Control: no-cache'), $postfields); }
额外验证要点
- 确保
redirect_uri在授权URL和获取Token请求中完全一致,包括协议(http/https)、域名、路径,不能有任何差异。 - 确认
getRadomString函数生成的state是足够随机的字符串,用于防止CSRF攻击。 - 使用
random_bytes()替代rand()生成随机数据,rand()是伪随机且安全性较低,random_bytes()符合密码学安全要求。
内容的提问来源于stack exchange,提问作者MAtt
相关产品推荐
相关产品推荐

