PKCE is how an MCP client proves that the authorization code it's redeeming is the one it asked for. The MCP authorization spec (2025-06-18) says "MCP clients MUST implement PKCE according to OAuth 2.1." The client creates a secret code_verifier and a code_challenge derived from it. The challenge goes out with the authorization request. The verifier stays with the client until the token request, where the client sends it. In the spec's words, this ensures "that only the original requestor can exchange an authorization code for tokens." If sign-in works but the token exchange fails, check the verifier before you rewrite anything else.
Who this is for
You're building an MCP client or a remote MCP server with OAuth, and you want to know what PKCE does, where each value goes, and how to tell a PKCE problem from everything else.
Where each value travels
| Step | What the client sends | What the spec says |
|---|---|---|
| 1. Before sign-in | Nothing yet. The client generates the PKCE parameters | The spec's sequence diagram starts with "Generate PKCE parameters" |
| 2. Authorization request | Authorization URL + `code_challenge` + `resource` | The browser opens the authorization URL with the challenge |
| 3. Token request | Authorization code + `code_verifier` + `resource` | The authorization server checks the verifier against the earlier challenge |
| 4. Calling the MCP server | Access token | The MCP server, as a resource server, validates the token |
The pairing matters because, as the spec puts it, PKCE "helps prevent authorization code interception and injection attacks by requiring clients to create a secret verifier-challenge pair." An attacker who intercepts the code doesn't have the verifier, so they can't redeem it. For the `resource` parameter that sits next to both values, see token audience and resource mismatch.
Pitfalls
- Generating a new verifier per request. The verifier sent with the token request has to be the one that produced the challenge for that same authorization request. If you regenerate it in between, nothing will match.
- Losing the verifier across a redirect. If the client restarts or loses state between the browser sign-in and the token request, the original verifier is gone.
- Treating PKCE as client authentication. PKCE protects one code exchange. It doesn't stand in for client registration. For when you need registration at all, see when you need DCR.
- Checking PKCE in the wrong place. The authorization server checks the verifier. An MCP server that only acts as a resource server validates the access token instead.
How to check it
- Log the `code_challenge` on the outgoing authorization URL.
- Log the `code_verifier` on the token request and confirm it's the value that produced that challenge, from the same sign-in attempt.
- Confirm both requests carry the same `resource` value.
- If the token request still fails, read the authorization server's error response before changing client code.
- Once a token comes back, debug the MCP server call separately. For the full auth sequence, see the MCP auth checklist and Protected Resource Metadata in plain English.
Where ShipKits fits
If you only need to understand PKCE, this post and the free templates at /free are enough. When you want a first desk day, the Day-1 Desk is a $12 pay-once first desk day: two seats (Day Desk + Reviewer Lite), one path-lock skill, a thin gate card, install.sh, and a ~90-minute sample run. Cheat Sheet for the skim checklist mindset.
Soft next step
Add the four log lines from "How to check it" to your client today and run one sign-in. Free starter files are at /free. When you want a first desk with a path-lock skill, the Day-1 Desk ($12) has it.