OAuth Authorization Code Flow with PKCE
The authorization code flow with PKCE is the recommended way for applications to obtain tokens on behalf of a user in OAuth 2.0, for web apps, single-page apps, mobile apps, and desktop apps alike. The user signs in at the authorization server (Google, Entra ID, Okta, Auth0, Keycloak, or your own), which redirects back to your app with a short-lived authorization code. Your app then exchanges that code, over a direct back-channel request, for access tokens (and optionally refresh and ID tokens).
PKCE (Proof Key for Code Exchange, pronounced "pixie") binds the code to the client that requested it, so an intercepted code is useless to an attacker. Once meant for mobile apps, PKCE is now recommended for all clients by the OAuth 2.1 draft and current security best practices. The older implicit flow, which returned tokens directly in the URL, is deprecated.
TL;DR
- The user is redirected to the authorize endpoint, authenticates, consents, and is redirected back with a code.
- The app exchanges the code at the token endpoint for tokens, a back-channel call that never exposes tokens in URLs.
- PKCE: the client sends
code_challenge = BASE64URL(SHA256(code_verifier))up front, and proves possession withcode_verifierat exchange time. - Validate a random
state(CSRF protection), register exact redirect URIs, and for OIDC use anonce. - Confidential clients (server apps) also authenticate with a secret or key; public clients (SPAs, mobile) rely on PKCE alone.
- For browser apps, the backend-for-frontend (BFF) pattern keeps tokens out of JavaScript entirely.
Quick Example
The flow, step by step:
Generating PKCE values (TypeScript):
In practice, use a certified library (for example openid-client, oauth4webapi, MSAL, AppAuth, or Spring Security) rather than hand-rolling the flow.
Core Concepts
Roles
Why a Code Instead of Tokens?
The front channel (browser redirects) is exposed: URLs leak through browser history, logs, referrer headers, and malicious extensions. The authorization code is short-lived (seconds to minutes), single-use, and useless without the back-channel exchange, which requires PKCE proof, and client authentication for confidential clients. Tokens therefore travel only over the direct TLS connection between the client and the token endpoint.
How PKCE Works
- The client creates a secret code verifier and sends only its SHA-256 hash (the code challenge) in the authorization request.
- The authorization server stores the challenge alongside the issued code.
- At token exchange, the client sends the verifier, the server hashes it, and it must match.
An attacker who steals the code (through a malicious app registered on the same custom URI scheme, a leaked log, or an open redirect) can't redeem it without the verifier. Always use S256, never plain.
state, nonce, and Redirect URIs
state: a random value tied to the user's session. Verify it on callback to prevent CSRF and login-injection attacks (PKCE also mitigates CSRF, but state is still recommended).nonce(OpenID Connect): included in the ID token, and verified to prevent token replay. See OpenID Connect.- Redirect URIs must be pre-registered and matched exactly. Wildcards and open redirects enable code theft.
Client Types
Mobile and desktop apps should use the system browser (ASWebAuthenticationSession, Custom Tabs), not embedded webviews, following RFC 8252. Webviews let the app observe credentials, and they break SSO.
SPAs and the Backend-for-Frontend Pattern
A pure SPA can run the flow in the browser with PKCE, but then access and refresh tokens live in JavaScript, where any XSS can steal them. The BFF pattern is recommended for sensitive apps:
- A lightweight backend acts as a confidential OAuth client, performs the code exchange, and stores tokens server-side.
- The browser gets only an HttpOnly, Secure, SameSite session cookie.
- The BFF proxies API calls, attaching access tokens.
This removes tokens from the browser entirely. See XSS and CSRF for the related protections.
Best Practices
Use PKCE Everywhere
Even confidential server apps should use PKCE. It defends against code injection, and it's mandated by OAuth 2.1 and the OAuth Security Best Current Practice (RFC 9700).
Use Certified Libraries
OAuth has many subtle validation steps (state, PKCE, issuer, audience, nonce, token signatures). Use well-maintained, OpenID-certified libraries for your platform.
Request Minimal Scopes
Ask for only the scopes the app needs, when it needs them (incremental consent). Broad scopes increase damage if tokens leak, and they reduce user trust.
Consider PAR for Higher Security
Pushed Authorization Requests (RFC 9126) send authorization parameters via a back-channel POST and pass only a reference in the browser redirect, which prevents parameter tampering and leakage. They're required in high-security profiles like FAPI 2.0.
Common Mistakes
Using the Implicit Flow
response_type=token returns access tokens in the URL fragment, where they're exposed to history, referrers, and scripts. It's deprecated. Use authorization code plus PKCE instead.
Loose Redirect URI Matching
Registering https://app.example.com/*, or allowing arbitrary subdomains, lets attackers redirect codes to pages they control. Register exact URIs per environment.
Skipping state Validation
Without verifying state, attackers can trick users into completing a login bound to the attacker's account (login CSRF), or inject stolen codes.
FAQ
What is PKCE and why is it needed?
PKCE is an extension where the client proves, at token exchange, that it's the same client that started the authorization request, using a secret verifier and its hashed challenge. It prevents stolen authorization codes from being redeemed, and it's now recommended for all OAuth clients, not just mobile apps.
Should single-page apps use the authorization code flow?
Yes, with PKCE. The implicit flow is deprecated. For sensitive applications, go further with a backend-for-frontend, so tokens never reach browser JavaScript.
What's the difference between the authorization code and the access token?
The authorization code is a short-lived, one-time credential delivered via the browser that proves the user authorized the client. The access token, obtained by exchanging the code over a secure back channel, is what the client presents to APIs.
Does PKCE replace the client secret?
For public clients that can't keep secrets, PKCE is the protection. Confidential clients should use both: client authentication proves the client's identity, and PKCE binds the code to the specific authorization request.
Related Topics
- OAuth — The framework overview
- OpenID Connect — Identity on top of the code flow
- OAuth Token Lifecycle — Access and refresh tokens after the exchange
- OAuth Security Best Practices — Hardening deployments
- SSO — Single sign-on built on these flows
- Authentication — Authentication concepts