Authorization Code Flow with PKCE

Authorization Code Flow with PKCE is an OAuth 2.0 pattern in which the client proves that it initiated an authorization request before exchanging the resulting authorization code. PKCE uses a one-time secret commitment to reduce the impact of an intercepted authorization code.

The client first creates a high-entropy `code_verifier` and derives a `code_challenge` from it, normally using `S256`. It sends the challenge with the authorization request. The authorization server associates that challenge with the resulting authorization code.

After the user authenticates and grants consent, the authorization server redirects the client with an authorization `code`. The code itself is short-lived and is intended to be exchanged only once. An attacker who obtains the code cannot normally redeem it without also knowing the original `code_verifier`.

The client sends the authorization code and its `code_verifier` to the token endpoint. The authorization server recomputes the challenge from the verifier and compares it with the challenge stored for that code. With `S256`, the relationship is `BASE64URL(SHA256(code_verifier)) = code_challenge`.

For a Java backend, PKCE complements normal OAuth client authentication rather than replacing it. A confidential server-side client can authenticate at the token endpoint while also using PKCE; the exact requirements depend on the authorization server and OAuth deployment. PKCE is especially important when an authorization code can potentially be intercepted before reaching the legitimate client.

Key points

Interview questions

What problem does PKCE solve?

PKCE reduces the risk associated with an authorization code being intercepted between the authorization server and the legitimate client. The client commits to a secret verifier through a challenge sent during authorization. When redeeming the code, it must provide the original verifier. Therefore, possession of the authorization code alone is insufficient to complete the exchange.

Walk me through the Authorization Code Flow with PKCE.

The client generates a random `code_verifier` and derives a `code_challenge`, normally with SHA-256. It sends the challenge to the authorization endpoint along with the requested authorization parameters. After authentication and consent, the authorization server returns a code to the registered redirect URI. The client sends that code and the verifier to the token endpoint, where the server validates the verifier before issuing tokens.

Why does PKCE use a challenge instead of sending the verifier immediately?

The challenge can be sent as part of the authorization request without revealing the secret verifier needed later. The authorization server records the challenge with the authorization code. During token exchange, the client reveals the verifier, allowing the server to recompute the challenge. An interceptor that sees only the authorization request and code does not obtain the verifier.

How would you implement PKCE in a Java backend?

I would use a well-maintained OAuth/OIDC client library where possible rather than implementing protocol details manually. The client transaction would generate a cryptographically secure verifier, derive the S256 challenge, and retain the verifier only for the corresponding authorization transaction. At the callback, the backend would associate the returned state and code with that transaction and send the verifier to the token endpoint. I would also enforce HTTPS, validate redirect URIs through the authorization-server registration, and protect against CSRF using the OAuth `state` mechanism where applicable.

Does PKCE make a confidential Java web application unnecessary to authenticate at the token endpoint?

No. PKCE and client authentication address different properties. PKCE demonstrates possession of the verifier associated with a particular authorization request, while client authentication can authenticate the registered OAuth client itself. A confidential backend may therefore use both mechanisms. The exact token-endpoint requirements are determined by the authorization server and the client's registered authentication method.