Securing Public Clients with OAuth 2.0 and PKCE
Learn how to implement OAuth 2.0 with PKCE to secure public clients like SPAs and mobile apps, eliminating the need for insecure client secrets.
04 Oct 2025, 20:56 UTC

The Problem: Client Secret Leakage in Public Apps
Traditional OAuth 2.0 Authorization Code grants rely on a client_secret to authenticate the application to the authorization server. However, "public clients"—such as Single Page Applications (SPAs) or mobile apps—cannot keep a secret. Any string embedded in JavaScript or a compiled binary can be extracted by a user or an attacker.
If an attacker intercepts the authorization code from the redirect URI, they can exchange it for an access token if they possess the client secret. Proof Key for Code Exchange (PKCE, pronounced "pixie") solves this by replacing the static secret with a dynamically generated, one-time cryptographic challenge.
Prerequisites
- An OAuth 2.0 compliant Authorization Server (e.g., Auth0, Okta, or Keycloak).
- A registered client application configured as a "Public Client" (meaning no client secret is required or stored).
- A strictly defined Redirect URI whitelist on the server to prevent open redirect attacks.
- A client-side environment capable of performing SHA-256 hashing.
Implementing the PKCE Flow
1. Generate the Code Verifier and Challenge
Before initiating the request, the client must create a code_verifier. This is a high-entropy cryptographic random string using unreserved characters [A-Z, a-z, 0-9, '-', '.', '_', '~], with a minimum length of 43 characters.
The client then creates a code_challenge by hashing the verifier using SHA-256 and encoding the result in Base64URL format.
// Conceptual JavaScript implementation
const verifier = generateRandomString(64);
const challenge = base64UrlEncode(sha256(verifier));
2. Request the Authorization Code
The client redirects the user to the /authorize endpoint. Unlike the standard flow, the client includes the challenge and the method used to create it.
Request Parameters:
response_type=codeclient_id=YOUR_CLIENT_IDredirect_uri=YOUR_REDIRECT_URIcode_challenge=THE_GENERATED_CHALLENGEcode_challenge_method=S256
3. Exchange Code for Token
After the user authenticates, the server redirects back to the client with a code. The client now sends a POST request to the /token endpoint. Instead of a client secret, the client sends the original code_verifier.
// POST to /token endpoint
// Headers: Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code
&client_id=YOUR_CLIENT_ID
&code=RECEIVED_AUTH_CODE
&redirect_uri=YOUR_REDIRECT_URI
&code_verifier=THE_ORIGINAL_VERIFIER
The server hashes the incoming code_verifier using the method specified in step 2. If the result matches the stored code_challenge, the server issues the access token.
Comparison: Standard Flow vs. PKCE
| Feature | Standard Auth Code Grant | Auth Code Grant + PKCE |
|---|---|---|
| Client Authentication | Static client_secret |
Dynamic code_verifier |
| Primary Risk | Secret leakage in source code | Authorization code interception |
| Client Type | Confidential (Server-side) | Public (Mobile/SPA) |
| Complexity | Lower | Higher (requires hashing) |
Verification and Diagnostics
To ensure the implementation is secure, perform the following checks:
- Verifier Mismatch: Attempt to exchange the authorization code using a modified or random
code_verifier. The server must return a400 Bad Requestwith aninvalid_granterror. - Secret Absence: Inspect the network traffic of the token request. Ensure no
client_secretis being transmitted from the browser or mobile device. - Code Single-Use: Attempt to use the same authorization code twice. The server should reject the second attempt immediately.
Limitations and Security Warnings
- Avoid "Plain" Method: Some servers support
code_challenge_method=plain. This provides no security benefit over the standard flow and should be avoided in favor ofS256. - Token Storage: PKCE secures the acquisition of the token, not the storage. Store tokens in HTTP-only, Secure cookies or encrypted platform storage to prevent XSS (Cross-Site Scripting) theft.
- Redirect URI Validation: Always use exact-match validation for redirect URIs on the server. Wildcards in redirect URIs can allow attackers to steal codes via open redirectors.
Rollback Procedure
If the PKCE implementation causes authentication failures due to incompatible client libraries, revert the client to the standard Authorization Code flow by removing the code_challenge and code_verifier parameters. Note that this requires the client to have a client_secret, which is insecure for public clients.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.