Navigate this guide · 1,546 words

The essential signal

  • PKCE binds a code exchange to a fresh transaction-specific verifier.
  • S256 sends a derived challenge instead of exposing the verifier in the authorization request.
  • The server must enforce the stored challenge and reject incorrect or downgraded exchanges.
  • PKCE complements redirect validation and the wider OAuth security design.

Authorization is a handoff, not a shared password

An OAuth authorization-code flow is an exchange with several distinct steps. The client initiates an authorization request, receives an authorization code, then presents that code at a token endpoint. The code is part of a handoff; it is not intended to stand in for every security check. RFC 7636 describes a problem affecting public clients: an attacker who intercepts the returned code may attempt to redeem it and obtain an access token.[1]

PKCE, short for Proof Key for Code Exchange and pronounced pixy, adds proof tied to that particular transaction. Its mechanism is different from choosing a stronger account password or replacing a password with a passkey. It protects a relationship between an authorization request and the later code redemption. This guide explains that relationship at a conceptual level. It is not a complete OAuth implementation recipe, a provider-specific integration guide or a claim that adding one parameter makes an application secure.[1]

The client creates a fresh verifier

Before the authorization request, the client generates a cryptographically random code verifier. RFC 7636 requires a high-entropy value and specifies its permitted character set and length. It also recommends a method using random octets encoded in a URL-safe form. The core property is not that the string looks complicated to a person; it is that an attacker cannot feasibly learn or guess the value used for that transaction.[1][2]

The verifier needs to be fresh for the transaction. The standard describes a unique verifier for every authorization request, while RFC 9700 emphasizes transaction-specific binding. Reusing a constant string would undermine the idea of proving possession of a secret created for the particular handoff. Production software should use an appropriate cryptographic random source and a maintained protocol library rather than an improvised naming scheme. A date, a user name or an easily predicted counter does not become a suitable verifier merely because it is placed in a field with the correct name.[1][2]

Unmarked key and lock-shaped tokens
Unmarked key and lock-shaped tokens. AI-generated illustrative artwork, not technical evidence or a product endorsement.

The challenge can travel without exposing the verifier

The client derives a challenge from the verifier and includes it in the authorization request. With the S256 method, the challenge is a base64url-encoded SHA-256 hash of the verifier. The authorization server associates that challenge and its method with the authorization code it later issues. This gives the server something to check when the code is redeemed, without placing the verifier itself in the initial authorization request.[1][2]

RFC 7636 retains a plain method for compatibility but requires clients that support S256 to use S256. RFC 9700 further recommends methods that do not expose the verifier in the authorization request and identifies S256 as the available method with that property. This is why “we sent a challenge” is an incomplete security description. The transformation method and the server's enforcement determine whether observing the authorization request also reveals the proof needed to redeem the code.[1][2]

Redemption checks the stored relationship

When redeeming the authorization code, the client sends the original verifier to the token endpoint. The server transforms the received verifier using the method associated with that code, then compares the result with the recorded challenge. RFC 7636 describes denying access when the values do not match. The useful mental model is an explicit relationship among one request, its issued code and the proof supplied for redemption.[1][2]

Under the standard's assumptions, a party holding only the intercepted code lacks the matching verifier. This is not encryption of the authorization code, and the verifier is not a reusable account credential. It is proof connected to that authorization transaction. Implementations must preserve that connection correctly on both sides. A client that computes a valid challenge is not protected by a server that quietly skips the comparison or associates the value with the wrong request.[1][2]

Hardware security token beside a laptop
Hardware security token beside a laptop. AI-generated illustrative artwork, not technical evidence or a product endorsement.

Modern guidance is broader than the original motivation

RFC 7636 addresses interception risks faced by public clients, including native applications. The later OAuth security best-current-practice document, RFC 9700, requires public clients to use PKCE and recommends it for confidential clients as well. It also discusses authorization-code injection. In that situation, binding a code to the correct challenge helps stop a different code from being substituted into the client's transaction.[1][2]

An older reference to public clients is not a reason to disregard newer guidance for another deployment. The current best-practice document addresses a larger collection of attacks and countermeasures. At the same time, PKCE does not eliminate the other responsibilities of a confidential client or turn a public client into one. The classification, credentials and provider behavior still need to be understood separately. Use the guidance appropriate to the complete flow rather than copying only the part that mentions your application type.[1][2]

Redirects and request binding remain security decisions

RFC 9700 requires exact redirect-URI matching, with a defined native-application exception for localhost port numbers. The redirect determines where an authorization response is delivered. A proof used at the token endpoint does not make arbitrary redirect destinations acceptable. Similarly, the document discusses binding the challenge securely to the client and user agent in which the transaction began.[2]

The best-current-practice document also says clients must confirm server support before relying on PKCE for CSRF protection. Other flows or circumstances require appropriate state or nonce handling as described by the protocol guidance. Avoid a slogan that PKCE universally replaces every use of state. Application state integrity, provider support and transaction binding are separate questions. A maintained library helps with mechanics, but its configuration still needs review against the intended authorization flow.[2]

Server, padlock and two different security tokens
Server, padlock and two different security tokens. AI-generated illustrative artwork, not technical evidence or a product endorsement.

Downgrade protection belongs on the server too

A PKCE-supporting server must enforce the verifier relationship when the authorization request contained a challenge. RFC 9700 also addresses downgrade attacks: a token request containing a verifier must not be accepted as though PKCE were valid when the corresponding authorization request had no challenge. Otherwise, an implementation can create a misleading appearance of protection around a transaction that was never correctly bound.[2]

Check whether the provider documents its support, exposes permitted challenge methods and rejects incorrectly bound exchanges. Do not test with a real user's sensitive credentials just to explore error cases. Use the provider's supported development environment and a legitimate test client. The point is to verify the security relationship, not merely see a successful sign-in screen. Success on the happy path does not show that incorrect verifiers or downgrade conditions are rejected.[2]

Review the exchange as one connected transaction

Review a complete transaction, not just the presence of a code_challenge parameter. Identify where the fresh verifier is created, where it is retained for the pending transaction, how the challenge is derived, and where the corresponding method is sent. RFC 7636 defines the relationship between these values. The server must associate the authorization request's challenge with the authorization code that it later issues. A challenge copied into a log or added to an unrelated request does not establish that relationship. The security property comes from the connected exchange, not the mere appearance of familiar names in an example URL.[1][2]

Follow that review through the code's redemption at the token endpoint. The client presents the verifier when exchanging its code, and the authorization server checks it against the stored challenge according to the chosen method. For S256, the documented transformation uses SHA-256 and base64url encoding without padding. These are implementation details to follow exactly, not approximate formatting choices. Use a maintained OAuth library where appropriate, and verify its documentation and configuration rather than designing an improvised substitute. A code example in an article cannot establish that your application's storage, redirects and error handling are correct. Treat the standard and provider documentation as the authoritative references for the actual integration.[1][2]

Make negative tests part of the review

A successful login proves that one intended path works; it does not prove invalid paths are rejected. In a controlled test environment, review the server behavior for a wrong verifier, a missing verifier where one is required, and a request that tries to change the expected challenge method. The relevant expected property is rejection of an exchange that does not satisfy the established relationship. Do not perform these checks against accounts or systems you are not authorized to test. The point is to confirm your own implementation's enforcement, not to turn a conceptual explainer into an instruction to probe someone else's service.[1][2]

RFC 9700 places PKCE alongside other OAuth security practices, including redirect handling and downgrade resistance. Keep those concerns in the same review without claiming that PKCE alone solves them. A correctly checked verifier does not prove that the user understood a permission request, that the requested scope was appropriate, or that tokens are handled safely after issuance. Record each security control and its intended failure mode separately. This makes it possible to say precisely what a test established: for example, an invalid verifier was rejected. That is stronger and more honest than declaring an entire login system secure because one PKCE-enabled happy path completed.[1][2]

The mechanism has a boundary

PKCE protects the code exchange; it does not establish the application's good intentions or protect every later use of a token. RFC 9700 separately covers redirect validation, token leakage and other threats because the flow has multiple security boundaries. An application still needs an appropriate token-handling design and the protections required for its platform and authorization server.[1][2]

When assessing an app, check whether its authorization is limited, understandable and revocable through the provider's controls. For developers, it is whether the whole flow follows current guidance and is implemented with maintained components. Neither audience should treat PKCE as a decorative checkbox or a substitute for review. Its value comes from a precise, enforced proof: the party redeeming this code must possess the verifier associated with the request that produced it.[1][2]

Questions, answered.

Is PKCE the same as a passkey?

No. PKCE is a proof used in an OAuth code exchange; a passkey is a public-key credential used for account authentication.[1]

Is the verifier a permanent client secret?

No. RFC 7636 describes a fresh high-entropy verifier for each authorization request.[1]

Should new clients use the plain challenge method?

A client capable of S256 must use it under RFC 7636. RFC 9700 recommends challenge methods that do not expose the verifier in the authorization request.[1][2]

Does PKCE remove all need for state checks?

No blanket claim is appropriate. RFC 9700 qualifies reliance on PKCE for CSRF protection and separately discusses transaction binding and state integrity.[2]

Read the sources.

  1. RFC 7636: Proof Key for Code ExchangeIETF · Accessed 2026-10-13
  2. RFC 9700: Best Current Practice for OAuth 2.0 SecurityIETF / RFC Editor · Accessed 2026-10-13
Something needs a closer look? Request a correction ↗
AI assistance and source checks reduce risk; they do not guarantee error-free reporting. See our editorial policy.