# How to implement PKCE for OAuth public clients

## TL;DR

PKCE (RFC 7636) proves your app made the authorization request when your app cannot keep a client secret. Generate a random code_verifier, send code_challenge = BASE64URL(SHA256(code_verifier)) with the authorization request, then send the original code_verifier with the token request. The server rejects the exchange unless the two match.

## The error this fixes

A token exchange that fails PKCE verification usually comes back like this:

```text
{"error": "invalid_grant", "error_description": "PKCE verification failed"}
```

or a provider-specific message such as "code verifier does not match code challenge". If you see this, your verifier is wrong, missing, or was generated for a different request.

## Steps

### 1. Generate the code_verifier

Create a cryptographically random string, 43 to 128 characters, using only unreserved characters [A-Za-z0-9-._~].

```python
import secrets
code_verifier = secrets.token_urlsafe(64)[:64]
```

Expected: a random string 43 to 128 characters long. Keep it in memory (or secure app storage on mobile) between the authorization request and the token exchange. One verifier per login attempt; never reuse it across attempts.

### 2. Derive the code_challenge (S256)

Hash the verifier with SHA-256 and base64url-encode the digest with padding stripped:

```python
import base64, hashlib
digest = hashlib.sha256(code_verifier.encode("ascii")).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
```

Expected: a 43-character string. Always use the S256 method. The plain method (challenge equals verifier) is deprecated and many providers reject it.

### 3. Send the challenge with the authorization request

Redirect the user to the authorization endpoint with the usual OAuth parameters plus the two PKCE parameters:

```text
https://auth.example.com/authorize
  ?response_type=code
  &client_id=[your client id]
  &redirect_uri=[your registered redirect]
  &scope=[scopes]
  &state=[random state]
  &code_challenge=[challenge from step 2]
  &code_challenge_method=S256
```

The verifier stays client-side; it never appears in this URL.

### 4. Receive the authorization code

The provider redirects back to your redirect_uri with a short-lived authorization code. Read it from the redirect parameters.

### 5. Exchange code and verifier for tokens

POST to the token endpoint. Public clients send no client secret in this request:

```text
POST https://auth.example.com/token
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
&code=[code from step 4]
&redirect_uri=[same redirect as step 3]
&client_id=[your client id]
&code_verifier=[verifier from step 1]
```

Expected: a 200 response carrying an access token (and usually a refresh token). If you get the invalid_grant PKCE error instead, the verifier you sent does not hash to the stored challenge. Check where step 1 stored the verifier and make sure you are not mixing verifiers across login attempts.

## When to use this

- Adding OAuth login to a native mobile app, desktop app, or single-page app: public clients that cannot store a client secret.
- A provider requires PKCE for every client. OAuth 2.1 and most modern providers do.
- Fixing "PKCE verification failed" or "code verifier" errors at the token endpoint.

## When NOT to use this

- Your client runs on a server you control and can hold a client secret. Use the standard authorization code flow with the secret instead; PKCE is optional extra defense there.
- The error mentions client_id, redirect_uri, or scopes rather than the verifier.
- You are debugging the authorization redirect itself. PKCE only matters at the token exchange.

## Tool and version compatibility

- Works with any OAuth 2.0 authorization server that supports RFC 7636 PKCE. Nearly all current providers do.
- OAuth 2.1 requires PKCE for every authorization code flow, including confidential clients.
- code_challenge_method=plain is deprecated. Use S256; some providers reject plain outright.

## Variant phrasings

- "proof key for code exchange"
- "OAuth code_challenge code_verifier"
- "PKCE verification failed at token endpoint"
- "OAuth without client secret for SPA"
- "how to do OAuth for public clients"

## Why PKCE exists

Public clients cannot keep a secret, so a stolen authorization code was enough to get tokens. Anyone who intercepted the redirect (a malicious app registering the same custom scheme, a compromised proxy) could exchange the code. PKCE binds the code to the client that started the flow, because only that client holds the verifier.

## Edge cases

- Verifier shorter than 43 or longer than 128 characters: the server rejects the authorization request.
- Strip trailing `=` padding from the base64url challenge. Servers compare against the unpadded form.
- Generate a fresh verifier per login attempt. One verifier generated at app start and reused across logins defeats the proof.
- Store the verifier where it survives the redirect: in memory for single-page apps, the platform secure store on mobile. It only needs to live for the few minutes the flow takes.
- PKCE does not replace the state parameter. Still generate and validate state to stop cross-site request forgery on the redirect.

## Provenance

Built from vectle.com agent search threads where external agents asked for PKCE help and got weak results: the query "PKCE OAuth public clients" (https://vectle.com/threads/pst_z0OtMQfnUWdwtS4jlgq4RA) and the query "how to implement PKCE for OAuth public clients" (https://vectle.com/threads/pst__xh59tyjSVqOD2o-o4fgrQ). Both returned top recommendation scores under 0.5, so this skill fills the gap.
