如何Implement OAuth 2.0 PKCE Flow
Implement Proof 关键为 Code Exchange (PKCE) 扩展到 OAuth 2.0 Authorization Code flow 为安全公共客户端认证.
问题
公共 clients (SPAs, mobile apps) cannot 安全存储客户端 secret, making 它们 vulnerable 到授权代码 interception attacks. 你需要implement PKCE (RFC 7636) 到绑定授权代码到 代码 verifier known 仅到 客户端.
前置条件
- Understanding 的 OAuth 2.0 Authorization Code flow
- 熟悉公共 vs. confidential clients
- Knowledge 的 SHA-256 哈希和 Base64URL 编码
步骤
-
1. Generate the code verifier and code challenge
Generate a cryptographically random code verifier: a high-entropy string of 43-128 characters from the unreserved character set [A-Z][a-z][0-9]-._~. Then derive the code challenge: S256 method computes BASE64URL-ENCODE(SHA256(ASCII(code_verifier))). The code challenge is sent in the authorization request; the code verifier is kept locally and never sent until the token exchange. Use the random generator for the verifier and SHA-256 + Base64URL for the challenge.
输入random 32 bytes -> base64url输出verifier: dBjftJeZ4CuK... | challenge: E9Melhoa2l2cYi8Yk... (S256) -
2. Send the authorization request with code challenge
Redirect the user to the authorization endpoint with parameters: response_type=code, client_id, redirect_uri, scope, state (CSRF protection), code_challenge, code_challenge_method=S256. The authorization server stores the code challenge associated with the issued authorization code. The state parameter must be a random value that the client validates on redirect to prevent CSRF.
输入GET /authorize?response_type=code&client_id=app1&code_challenge=E9Mel...&code_challenge_method=S256&state=xyz输出redirect: https://app.com/cb?code=SplxlOBeZQQYbYS6WxSbIA&state=xyz -
3. Exchange the code with the code verifier
On receiving the authorization code, send a token request to the token endpoint with: grant_type=authorization_code, client_id, code, redirect_uri, code_verifier. The authorization server recomputes BASE64URL(SHA256(code_verifier)) and compares it to the stored code challenge. If they match, the code was not intercepted — issue tokens. If they don't match, reject the request. Use the OAuth token parser to inspect the returned tokens.
输入POST /token { grant_type, code, code_verifier: dBjftJeZ4CuK... }输出{ access_token, refresh_token, token_type: Bearer, expires_in } -
4. Validate tokens and secure the state parameter
Validate the state parameter matches the one sent in step 2 to prevent CSRF. Parse the access token (if JWT) to check expiration, issuer, and audience. Store tokens securely: access token in memory, refresh token in secure storage (httpOnly cookie for web, secure enclave for mobile). Never store tokens in localStorage for SPAs. Use the JWT parser to verify token claims before using the token.
输入access_token: eyJhbGciOi...输出valid: true | exp: 1234567890 | scope: read write
预期结果
安全 OAuth 2.0 PKCE flow where 公共 clients 认证无 客户端 secret, 受保护针对授权代码 interception 和 CSRF attacks.
常见错误
- Using plain code_challenge_method instead of S256
- The 'plain' method sends the verifier directly as the challenge, providing no protection if intercepted. Always use S256 (SHA-256 based).
- Code verifier too short or not cryptographically random
- The verifier must be 43-128 characters of high entropy. Use a CSPRNG (crypto.getRandomValues), not Math.random(). 32 random bytes base64url-encoded gives 43 characters.
- Omitting or not validating the state parameter
- The state parameter prevents CSRF. Generate it randomly, include in the auth request, and validate it exactly matches on the redirect.
排障指引
- If the authorization server rejects the token request with 'invalid_grant', verify the code_verifier matches the code_challenge used in the authorization request.
- For SPAs, consider using the Backend-For-Frontend (BFF) pattern instead of storing tokens in the browser, which is more secure than pure PKCE in the browser.
- If using refresh tokens, implement token rotation (issue a new refresh token on each use) and detect reuse to revoke the token family.
安全限制
- PKCE protects the authorization code exchange but does not protect the access token from XSS in the browser — use BFF pattern for higher security.
- All token validation happens locally; the client never sends the code verifier to any party except the authorization server.
- Access tokens in localStorage are vulnerable to XSS — prefer in-memory storage with silent refresh via refresh token.