PKCE vs State vs Nonce: Understanding OAuth Security Parameters at the Edge

· Sarah Mitchell · 10 min read

OAuth 2.0 authorization flows carry three security parameters that look similar on the surface but protect against entirely different attacks. The state parameter prevents cross-site request forgery. The nonce parameter prevents ID token replay. PKCE (Proof Key for Code Exchange) prevents authorization code interception. Engineers building authentication infrastructure behind CDN edge proxies need to understand not just what each parameter does, but how they interact with reverse proxy layers, session management, and the redirect-heavy nature of authorization code flows that traverse multiple network hops between client, authorization server, and resource server.

What Is PKCE and How Does It Prevent Authorization Code Interception?

PKCE (RFC 7636, pronounced "pixy") was designed to solve a specific attack vector: authorization code interception during the redirect back from the authorization server to the client. In the standard authorization code flow, the authorization server redirects the user's browser to the client's redirect URI with the authorization code in the query string. If an attacker can intercept this redirect — through a malicious app registered for the same custom URI scheme, a compromised browser extension, or network-level interception — they obtain a valid authorization code and can exchange it for tokens.

PKCE prevents this by creating a cryptographic binding between the authorization request and the token exchange request. Before starting the flow, the client generates a high-entropy random string called the code_verifier (43-128 characters from the unreserved character set). It then computes the code_challenge as the base64url-encoded SHA-256 hash of the verifier. The authorization request includes the code_challenge and code_challenge_method=S256. The authorization server stores this challenge alongside the authorization code it issues.

When the client exchanges the authorization code for tokens, it sends the original code_verifier (not the challenge). The authorization server hashes the verifier, compares it to the stored challenge, and only issues tokens if they match. An attacker who intercepts the authorization code cannot complete the exchange because they never had the code_verifier — it was never transmitted through the redirect URI or any browser-visible channel.

# Step 1: Client generates PKCE values
code_verifier  = "dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk"
code_challenge = base64url(sha256(code_verifier))
               = "E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM"

# Step 2: Authorization request includes challenge
GET /authorize?
  response_type=code&
  client_id=app123&
  redirect_uri=https://app.example.com/callback&
  code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&
  code_challenge_method=S256&
  state=xyz789&
  nonce=abc456

# Step 3: Token exchange includes verifier
POST /token
  grant_type=authorization_code&
  code=AUTH_CODE_HERE&
  redirect_uri=https://app.example.com/callback&
  code_verifier=dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk
PKCE Authorization Code Flow with CDN Edge Proxy PKCE Flow: How Code Interception Is Prevented Client Holds verifier CDN Edge Pass-through Auth Server Stores challenge Attacker No verifier 1. /authorize + code_challenge 2. Forward challenge (pass-through) 3. Redirect with auth code in URL 3a. Code intercepted! 4. POST /token + code + code_verifier ✓ 5. POST /token + code (no verifier) sha256(verifier) = challenge? ✓ Token issued to legitimate client No verifier → 400 Bad Request ✗
Figure 1: PKCE prevents authorization code interception by binding the code to a secret verifier

How Does the State Parameter Prevent CSRF Attacks on OAuth Flows?

The state parameter protects against a different attack entirely: cross-site request forgery (CSRF) against the OAuth redirect URI. Without state, an attacker can craft a redirect URI containing their own authorization code and trick the victim's browser into visiting it. The client application processes the redirect, exchanges the attacker's code for the attacker's tokens, and binds the victim's session to the attacker's account. The victim is now operating in the attacker's context without realizing it.

Performance Comparison: Edge vs Origin Processing
Metric Edge Processing Origin Processing Improvement
Latency (p50)8–15 ms120–300 ms15–20x faster
Cache Hit Ratio85–95%N/AReduces origin load
TLS Handshake1–5 ms (nearby PoP)50–150 ms10–30x faster
Bandwidth Cost$0.01–0.04/GB$0.05–0.12/GB3–5x cheaper
Availability99.99% (multi-PoP)99.9% (single DC)10x fewer outages

The state parameter works by creating a session-specific binding between the authorization request and the callback. The client generates a cryptographically random value, stores it in the user's session (typically in a secure, HttpOnly cookie or server-side session store), and includes it as the state parameter in the authorization request. The authorization server echoes the state back unchanged in the redirect. The client then verifies that the returned state matches the stored value before processing the authorization code.

An attacker cannot forge a valid state value because they do not have access to the victim's session storage. If the attacker crafts a redirect with their own code but without the correct state (or with a state from a different session), the client rejects the callback and the CSRF attack fails.

In CDN edge proxy deployments, state management becomes architecturally significant. When the edge function acts as a BFF (Backend-for-Frontend), it generates and validates the state parameter on behalf of the browser client. The state value and its associated session must be accessible across edge PoPs in case the authorization redirect routes through a different PoP than the one that initiated the flow. This requires either a globally replicated session store or a self-contained encrypted state cookie that any edge PoP can decrypt.

What Role Does the Nonce Parameter Play in OpenID Connect?

The nonce parameter is specific to OpenID Connect and protects the ID token against replay attacks. When a client receives an ID token (either from the token endpoint or directly in the authorization response in implicit/hybrid flows), it must verify that the token was issued for this specific authentication request. Without nonce binding, an attacker who captures an ID token could replay it to authenticate as the victim in a different session or at a different relying party.

The nonce flow mirrors the state parameter: the client generates a random value, includes it in the authorization request, and the authorization server embeds the same value in the nonce claim of the issued ID token. The client verifies the nonce claim matches the expected value before accepting the ID token. Unlike state, which is returned as a query parameter in the redirect URI, the nonce is cryptographically bound inside the signed JWT, preventing modification by intermediaries.

For CDN architectures processing OpenID Connect flows, the nonce provides an additional guarantee that edge security layers can verify. An edge server validating ID tokens can check that the nonce claim is present, matches the expected format, and has not been seen before in a recent time window. A nonce cache at the edge, similar to the DPoP jti cache, prevents token replay even if the client application fails to implement its own nonce verification.

How Do These Parameters Interact with CDN Edge Proxy Architecture?

CDN edge servers interact with OAuth security parameters in three distinct modes depending on their role in the authentication architecture. In pass-through mode, the edge server simply forwards authorization requests and callbacks without modification. In this mode, the edge must ensure it does not strip, cache, or log the code_challenge, code_verifier, state, or nonce parameters. Query string caching rules must exclude authorization endpoints, and security header configurations must not interfere with the redirect chain.

In BFF proxy mode, the edge server acts as the OAuth client on behalf of the browser. The edge function generates the PKCE code verifier, computes the challenge, creates the state value, generates the nonce, initiates the authorization redirect, receives the callback, performs the token exchange (including sending the code verifier), validates the ID token's nonce claim, and establishes a session with the browser using a secure cookie. All three security parameters are managed entirely at the edge, with the browser never touching raw tokens or security parameters.

In validation mode, the edge server validates tokens presented by clients but does not participate in the authorization flow itself. The edge verifies access token signatures, checks expiration, validates audience claims, and optionally checks DPoP binding. In this mode, PKCE and state are irrelevant (they are consumed during token issuance), but the nonce in cached ID tokens may be checked to prevent token reuse across sessions.

CDN Edge Interaction Modes with OAuth Security Parameters Edge Proxy Modes for OAuth Security Parameters Pass-Through Mode Edge forwards all params unchanged PKCE: • Don't strip code_challenge • Don't log code_verifier State: • Don't cache redirect URIs • Preserve query params Nonce: • Transparent to edge Risk: • Accidental caching of auth endpoints leaks codes/state BFF Proxy Mode Edge manages all security params PKCE: • Edge generates verifier • Stores in edge KV (encrypted) State: • Edge creates + validates • Encrypted state cookie or KV Nonce: • Edge generates + verifies • ID token validated at edge Risk: • Cross-PoP state sync needed Validation Mode Edge validates tokens only PKCE: • Not applicable (consumed during token issuance) State: • Not applicable Nonce: • Check in ID token if cached • Replay cache at edge Focus: • JWT sig, exp, aud, scope
Figure 2: Three modes of CDN edge interaction with OAuth security parameters

Why Does OAuth 2.1 Require PKCE for All Client Types?

OAuth 2.1 (the consolidation of OAuth 2.0 and its security best practices) mandates PKCE for all clients, eliminating the distinction between public and confidential clients for PKCE purposes. This decision addresses a subtle attack called authorization code injection that affects confidential clients even though they have a client secret.

In an authorization code injection attack, the attacker obtains a valid authorization code (through phishing, social engineering, or by initiating an OAuth flow at a legitimate client and capturing the code from the redirect). The attacker then injects this code into the victim's callback session. The client application, having a valid client secret, successfully exchanges the injected code for tokens. The victim's session is now bound to whatever identity the attacker's code represents.

The client secret proves the client's identity to the authorization server, but it does not prove that the specific authorization code was intended for this specific flow initiation. PKCE provides that binding: even if the attacker obtains a valid code, they cannot inject it into a victim's flow because the victim's flow has a different code_verifier stored in its session. The authorization server will reject the exchange because the victim's verifier does not match the challenge associated with the attacker's code.

For CDN-hosted applications using API gateway authorization, the OAuth 2.1 PKCE mandate simplifies edge configuration. The edge proxy can enforce a universal PKCE policy: reject any authorization request that does not include code_challenge and code_challenge_method=S256. This zero-trust approach catches misconfigured clients that might accidentally omit PKCE, closing the code interception and injection attack surfaces at the edge before the request reaches the authorization server.

What Are the Common Implementation Mistakes with These Parameters?

The most dangerous PKCE mistake is using the plain code challenge method, where the challenge equals the verifier. This eliminates the security guarantee entirely: if an attacker intercepts the authorization request (which is transmitted through the browser), they obtain the challenge, which in plain mode is the verifier itself. Always use S256. Authorization servers should reject plain method requests, and edge rate limiting should flag clients attempting to use it.

For state, the common mistake is using predictable values (sequential numbers, timestamps, or user IDs). The state must be a cryptographically random value with at least 128 bits of entropy. Another mistake is validating state by comparing it across sessions rather than within a session. The state check must verify the value against the same session that initiated the flow, not against a global database of valid state values.

Nonce implementation often fails by storing the nonce in browser-accessible storage (localStorage), making it vulnerable to XSS. The nonce should be stored in a secure, HttpOnly cookie or server-side session. For edge-managed nonce validation, the nonce can be derived from the session identifier using an HMAC, eliminating the need for separate nonce storage while maintaining the binding between authentication request and ID token.

A cross-cutting mistake is failing to bind these parameters to each other. Each parameter should be linked to the same session context. If an edge proxy generates PKCE values, state, and nonce independently and stores them in separate locations, an attacker might combine components from different sessions. Using a single session identifier as the seed for deriving all three values ensures they are cryptographically bound to the same flow initiation.

How Should Edge Servers Log and Monitor OAuth Security Parameters?

OAuth security parameters require careful handling in CDN monitoring and logging systems. The code_verifier must never appear in logs — it is the secret that protects the authorization code exchange, and logging it would expose it to anyone with log access. The code_challenge can be logged safely since it is a one-way hash. The state value can be logged for debugging redirect flows, but its association with a specific user session should not be loggable by default to prevent session tracking.

Monitoring should track several metrics related to these parameters. PKCE adoption rate measures what percentage of authorization flows include PKCE parameters, with the goal of 100%. State validation failure rate detects CSRF attempts — a spike in state mismatches indicates an active attack. Nonce reuse detection catches ID token replay attempts. Code challenge method distribution should show 100% S256; any plain usage indicates a misconfigured or malicious client.

Edge servers in BFF mode should emit structured log events for each stage of the OAuth flow: flow initiated (with code_challenge hash), redirect received (with state validation result), token exchange completed (success/failure, never the verifier), and ID token validated (nonce check result). These events enable end-to-end flow tracing without exposing sensitive parameter values, supporting both performance debugging and security incident investigation across the distributed CDN infrastructure.

Understanding the distinct roles of PKCE, state, and nonce is essential for engineers implementing OAuth flows in CDN-heavy architectures. These parameters are not interchangeable and not redundant — each blocks a specific attack that the others do not. Deploying all three correctly, with proper edge proxy handling, parameter isolation, and monitoring, creates a defense-in-depth authentication flow that resists code interception, CSRF, and token replay simultaneously. As OAuth 2.1 adoption accelerates, these security parameters transition from best practices to mandatory requirements, and CDN security infrastructure must be configured to support them transparently and correctly.

External References

References & Standards: HTTP/2 specification · RFC 7234