How Does RFC 7636 Proof Key for Code Exchange (PKCE) Work?

Clifford Spielman Technical Writer

RFC 7636 Explained: How PKCE Protects the OAuth Authorization Code Flow

Proof Key for Code Exchange, often called OAuth PKCE, is defined by RFC 7636. It protects the OAuth 2.0 authorization code flow against authorization-code interception and injection. The client application creates a code_verifier for each authorization request and retains it, sends a derived code_challenge with the authorization request, and later sends the verifier when exchanging the code for tokens.

Why OAuth RFC 7636 Is Needed

In the OAuth 2.0 authorization code flow, the authorization server first returns a short-lived authorization code through the user agent. The client then sends that code to the token endpoint and exchanges it for tokens. Until it is redeemed, the authorization code is sensitive because an attacker who obtains and successfully exchanges it may receive tokens representing the user’s authorization.

Another important risk is authorization-code injection. In an injection attack, the attacker attempts to make a client accept an authorization code from a different authorization request. PKCE protects against this by binding each authorization request to the unique code_verifier. When the legitimate client later presents its verifier, the authorization server rejects an injected code that was created with a different challenge.

RFC 7636 was originally motivated by authorization-code interception attacks affecting public clients such as native and desktop applications. These clients cannot reliably keep static client secrets confidential because the software and any embedded credentials are distributed to users.

To illustrate this threat, RFC 7636 presents a specific scenario involving native applications, where more than one application on a device may be able to register for the same custom redirect URI scheme. A malicious application can then receive an authorization response intended for the legitimate application. The RFC also considers a more sophisticated attacker that observes authorization requests through leaked operating system HTTP logs, but cannot act as a man-in-the-middle.

PKCE addresses these problems without requiring reusable secrets. For each authorization request, the client creates the high-entropy code_verifier value and derives the code_challenge from it. The challenge travels with the authorization request, while the verifier remains with the client until the token request. An intercepted authorization code is therefore not enough by itself to obtain tokens.

How OAuth RFC 7636 Works

PKCE, as defined in RFC 7636, adds no new endpoint or metadata document. Rather, it adds parameters to the existing authorization and token requests in the authorization code flow.

Parameter Name Where It's Used Purpose

code_verifier

Created and retained by the client; later sent to the token endpoint

Per-request secret used to prove possession

code_challenge

Sent with the authorization request

Enables later verification of the code_verifier

code_challenge_method

Sent with the authorization request

Identifies how the challenge was derived; new implementations should use S256 (SHA-256)

The PKCE flow consists of three main steps:

Step 1: Create the Verifier and Challenge

Before initiating an authorization request, the client application generates a random code_verifier. RFC 7636 permits 43 to 128 characters from the unreserved URI character set and recommends at least 256 bits of entropy. One recommended method is to generate 32 random octets and base64url-encode them, producing a 43-character verifier.

When S256 is used, the client derives the challenge as follows:

code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

RFC 7636 also defines plain as an alternative challenge method to S256. With plain, no SHA-256 transformation is applied, therefore the code_challenge is the same value as the code_verifier. The plain method exists for compatibility and constrained environments. It should not be used in new implementations when S256 is available.

The code_challenge_method parameter is optional and RFC 7636 defaults it to plain when it is omitted. A client using S256 must therefore send the code_challenge_method parameter with a value of S256 in the authorization request. Otherwise, the authorization server assumes plain and later compares the verifier directly with the SHA-256-generated challenge. Because those values do not match, the token request fails.

Step 2. Send the Challenge with the Authorization Request

The client application then sends the challenge and challenge method to the authorization endpoint. For example, an authorization request using PKCE might look like the following:

GET /authorize?
response_type=code&
client_id=client&
redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback&
state=abc123&
code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
code_challenge_method=S256 HTTP/1.1
Host: authorization.example.com

The query parameters are shown on separate lines for readability. In an actual HTTP request, the request target is sent on a single line.

If the authorization request succeeds, the server issues an authorization code as usual. As part of PKCE, it also associates that code with the code_challenge and code_challenge_method so they can be used later to verify the code_verifier presented by the client. The code_verifier is not sent yet. The client application keeps it until the next step, when it exchanges the authorization code for tokens.

Step 3. Send the verifier with the token request

After the client receives the authorization code, it sends the code and the original verifier to the token endpoint. Again, the sample endpoint is formatted for readability:

POST /token HTTP/1.1
Host: authorization.example.com
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code&
code=<authorization-code>&
redirect_uri=https%3A%2F%2Fclient.example.com%2Fcallback&
client_id=client&
code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk

The authorization server then applies the recorded challenge method to the provided code_verifier value and compares the result to the challenge associated with the authorization code. When S256 is used, the comparison is:

BASE64URL-ENCODE(SHA256(ASCII(code_verifier))) == code_challenge

If the values match, token processing continues. If they do not match, the authorization server rejects the request. An attacker who intercepts the authorization code cannot successfully redeem it because they do not know the corresponding code_verifier.

sequenceDiagram
    participant Client as OAuth Client
    participant Browser as User Agent
    participant AS as Authorization Server


    Client->>Client: Generate code_verifier and S256 code_challenge
    Client->>Browser: Start authorization request with code_challenge + S256 method
    Browser->>AS: Authorization request
    AS-->>Browser: Redirect with authorization code
    Browser-->>Client: Redirect to client with authorization code
    Client->>AS: Token request with code + code_verifier
    AS->>AS: Recompute challenge and compare
    AS-->>Client: Return tokens if all checks succeed

RFC 7636 vs. Related OAuth Specifications

RFC 7636 defines PKCE, but several other OAuth specifications determine how it is used and how authorization servers can advertise their support for it. Together, these specifications show how PKCE has evolved from a protection aimed primarily at public native clients into a broader OAuth security measure used across a wider range of clients, including web applications.

OAuth Specification What it Contributes Relationship to PKCE

RFC 7636

Defines the code_verifier, code_challenge, challenge methods, and verification process

Defines the PKCE mechanism

RFC 6749

Defines the core OAuth 2.0 framework, including the authorization code flow and its authorization and token endpoints

Defines the flow that PKCE extends and protects

RFC 8252

Defines OAuth 2.0 best practices for native apps, including use of external user agents and secure redirect URI patterns

Requires public native apps to use PKCE and authorization servers to support it for those clients

RFC 8414

Defines a standard metadata document that lets clients discover an authorization server’s endpoints, capabilities, and supported features

Lets an authorization server advertise its supported PKCE challenge methods through code_challenge_methods_supported

RFC 9700

Defines current OAuth 2.0 security best practices, updating earlier guidance to address newer threats and lessons learned from real-world deployments

Requires PKCE for public clients, recommends it for confidential clients, and requires authorization servers to support it

OAuth 2.1 (Internet-Draft)

Consolidates OAuth 2.0 and later security guidance into an updated authorization framework

Incorporates PKCE into the authorization code flow and requires its use by default

Security Considerations: Verifier Security and Downgrade Protection

PKCE depends on the code_verifier being both unpredictable and transaction-specific. RFC 7636 recommends at least 256 bits of entropy and a unique verifier for every authorization request. Reusing a code_verifier or making it predictable weakens that protection because the verifier links the token request back to the original authorization request.

Clients should use S256 as the code_challenge_method. With plain, the challenge is the verifier itself, so an attacker who can observe the authorization request may learn the value needed to redeem an intercepted code. Clients also should not fall back to plain if an S256 request fails. Authorization servers that support PKCE are required to support S256, while servers that do not support PKCE simply ignore the PKCE parameters. An S256 error therefore points to either a server problem or a possible downgrade attack, rather than a reason to switch to plain.

RFC 9700 reinforces this guidance. It recommends challenge methods that do not expose the verifier in the authorization request, and S256 is currently the only defined method with that property.

In a PKCE downgrade attack, an attacker removes the code_challenge from the authorization request and obtains a code that is not bound to PKCE, then injects that code into the victim’s session. The victim’s client later sends its own code_verifier, but a vulnerable server may skip verification because no challenge was recorded. RFC 9700 requires the server to reject a token request containing a code_verifier when the corresponding authorization request contains no code_challenge.

PKCE strengthens the authorization code flow, but it does not replace the other protections OAuth relies on. Clients and authorization servers still need TLS, correct redirect URI validation, and client authentication where appropriate. By binding an authorization code to the client’s code_verifier, PKCE ensures that intercepting the code alone is not enough to obtain tokens.

FAQs

Does PKCE Replace Client Authentication or the Need for a Client Secret?

No. PKCE and client authentication solve different problems. Client authentication confirms a confidential client's identity at the token endpoint, while PKCE binds an authorization code to a per-request code_verifier.

Even strong client authentication does not prevent authorization-code injection, since the intended client is the one authenticating when it redeems the injected code. PKCE adds a separate protection by ensuring that a stolen or injected authorization code cannot be used without the corresponding verifier. Confidential clients can therefore use both client authentication and PKCE.

Is PKCE Limited to Mobile Apps and Other Public Clients?

No. RFC 7636 was designed around public-client and native-app threats, but current OAuth security guidance is broader. RFC 9700 requires PKCE for public clients, recommends it for confidential clients, and explicitly says the guidance applies to all kinds of OAuth clients, including web applications.

How do the code_verifier and code_challenge Differ?

The code_verifier is a random value the client generates and retains for each authorization request. The code_challenge is derived from that verifier and sent with the authorization request. When using S256, the client applies SHA-256 to the verifier and base64url-encodes the result to create the challenge.

Does PKCE Eliminate the Need for the OAuth state Parameter?

Not always. A client can rely on PKCE for CSRF protection when it has confirmed that the authorization server supports PKCE. Otherwise, it should use state for CSRF protection. In OpenID Connect, the nonce parameter can provide similar protection by letting the client verify that the ID Token corresponds to its original authentication request. Separate from these security considerations, applications may still use state to carry application-specific information, in which case that value should also be protected against tampering.

Further Reading