Skip to main content
Agent Identity

IETF · September 2015

Proof Key for Code Exchange by OAuth Public Clients

RFC 7636

N. Sakimura, J. Bradley, N. Agarwal

RFCStableAuthenticationFoundationField guide

Proof Key for Code Exchange by OAuth Public Clients (RFC 7636) is RFC, September 2015. An agent that opens a browser (or a loopback redirect) to get user consent is a public or at-best confidential client in a hostile environment. PKCE is the difference between 'the code in the redirect is enough' and 'only the process that started the flow can finish it'.

At a glance

Problem
Public clients (native apps, many SPAs, many agents) cannot keep a client secret. An attacker who intercepts the authorization code can otherwise redeem it.
Identity / authn / authz
Protects the authorization-code redemption step (a form of client-to-AS authentication of the token request). Does not identify the user.

Actors

  • Public or confidential client
  • Authorization server

How PKCE works

Animated sequence · PKCE

PKCE — Why can't a stolen authorization code be redeemed?

Only the process that started the flow can finish it

The client keeps a high-entropy code_verifier. The authorization server only sees a hash (code_challenge, S256). An interceptor who steals the code from the redirect cannot redeem it. OAuth 2.1 keeps only S256; plain is gone. Authentication and consent stay separate hops.

Client (agent)same party
Authorization server
RO (user)

Now · 1/9· Prove· RFC 7636You are here

Client (agent)→Client (agent)·Creates a code_verifier and keeps it

The client generates a fresh code_verifier and holds it in the process that will later call the token endpoint.

Why The secret must never appear on the front channel. A static challenge is not PKCE. Generate a new verifier per authorization request.

Write a secret in the desk drawer.

  • code_verifier from the spec

Solid = from the spec

Step 1 of 9: agent to agent. Creates a code_verifier and keeps it RFC 7636
Speed
ShareLinkedInX

RFC 7636. OAuth 2.1 keeps only S256; the plain method is gone. Generate a new verifier per authorization request.

Hop list

Sequence above; this list is the same hops for reading and search. Each row is one message.

  1. 1

    Client (agent)→Client (agent)RFC 7636Creates a code_verifier and keeps it

  2. 2

    Client (agent)→Client (agent)RFC 7636Derives code_challenge = S256(code_verifier)

  3. 3

    Client (agent)→Authorization serverRFC 7636Authorization request carries code_challenge, not the verifier

  4. 4

    RO (user)→Authorization serverOAuth 2.1Authenticates at the authorization server

  5. 5

    RO (user)→Authorization serverOAuth 2.1Consents to this client

  6. 6

    Authorization server→Client (agent)OAuth 2.1Authorization code in the redirect (an interceptor might see this)

  7. 7

    Client (agent)→Authorization serverRFC 7636Token request: code + the original code_verifier

  8. 8

    Authorization server→Authorization serverRFC 7636Hashes the verifier with S256 and matches the stored challenge

  9. 9

    Authorization server→Client (agent)RFC 6750Match — access token. Stolen code without the verifier fails

01

The gap for agents

Public clients cannot keep a client secret. An attacker who intercepts the authorization code — custom URI schemes, referrer leakage, a malicious app on the device, a compromised agent helper — can redeem it. PKCE binds redemption to the process that started the request.

Agents that open a browser, a loopback redirect, or a device-code-adjacent window are in this threat model even if someone labelled them 'confidential' in a portal. OAuth 2.1 and RFC 9700 require PKCE for authorization-code clients including confidential ones, because authorization-code injection is not only a native-app problem.

02

Actors and trust boundaries

The authorization code travels through the user-agent. The code_verifier never does: it stays in the client and appears only on the token-endpoint back channel. The AS is trusted to store the challenge and compare. The RS never sees PKCE parameters.

03

Mechanics

Client creates a high-entropy code_verifier. code_challenge = BASE64URL(SHA256(verifier)) with code_challenge_method=S256. Authorization request carries the challenge; token request carries the verifier. AS accepts the code only if SHA256(verifier) matches. RFC 7636 also defined plain; OAuth 2.1 prohibits it.

Generate a new verifier per authorization request. Do not log verifiers. Pair with exact redirect matching and, for mix-up, RFC 9207 iss.

code_challenge_method=S256from spec
Quoted as the only method OAuth 2.1 keeps. plain is prohibited.
code_verifierfrom spec
Quoted RFC 7636: cryptographically random string, sent only to the token endpoint.

04

Step-by-step flows

Flow 1

PKCE on authorization code

  1. 1

    Client generates code_verifier, derives S256 code_challenge.

  2. 2

    Authorization request includes code_challenge and code_challenge_method=S256.

  3. 3

    After redirect, token request includes code and code_verifier (plus client auth if any).

  4. 4

    AS recomputes S256 and compares. Mismatch → invalid_grant.

05

Identity vs authentication vs authorization

Protects the authorization-code redemption step — client-to-AS authentication of the token request, not user identity. Authorization of the API remains the access token.

06

How it composes

  • OAuth 2.1

    Folded into the core grant.

  • OAuth for native apps

    RFC 8252 required PKCE for native apps; 2.1 generalized it.

  • MCP authorization

    MCP clients doing authorization code MUST use PKCE as OAuth 2.1 clients.

  • DPoP

    PKCE protects the code; DPoP protects later resource requests. Both are needed if the agent is public and the token is valuable.

07

What bites agent implementers

  • Reuse of verifiers

    A static challenge is not PKCE. One verifier per request.

  • plain method

    If the AS still allows plain, an interceptor who sees the challenge sees the verifier. Refuse to use plain.

  • PKCE is not sender-constraint of the access token

    After redemption, a stolen Bearer access token still works. Add DPoP/mTLS/signatures.

08

Stability — what you can ship

RFC 7636, September 2015, N. Sakimura, J. Bradley, N. Agarwal. Stable. Required by RFC 9700 and OAuth 2.1. https://www.rfc-editor.org/rfc/rfc7636.html

Catalog claims (short form)

code_challenge_method=S256
The only method OAuth 2.1 keeps. 'plain' is prohibited.

Implementer notes

OAuth 2.1 and RFC 9700 require PKCE for authorization-code clients, including confidential ones. Generate a new verifier per authorization request.

Related

Primary sources

  • RFC 7636https://www.rfc-editor.org/rfc/rfc7636.html