Authorization Code Flow + PKCE
Why Authorization Code + PKCE?
Section titled “Why Authorization Code + PKCE?”This is the recommended flow for public clients — SPAs and mobile apps — that cannot keep a client secret secure. The older Implicit flow is deprecated and must not be used in new projects.
PKCE (Proof Key for Code Exchange) protects against authorization code interception attacks. Because a public client cannot hold a secret, PKCE replaces it: the app generates a one-time secret (code_verifier) at the start of each flow, derives a code_challenge from it, and later proves it holds the original verifier when exchanging the code for tokens.
The actors and their roles
Section titled “The actors and their roles”The user’s browser drives the redirect dance. Your SPA or mobile app (the client) initiates the flow and handles the redirect callback. Keycloak (the authorization server) authenticates the user and issues tokens. Your API (the resource server) accepts the resulting access token to authorize calls.
Step-by-step flow
Section titled “Step-by-step flow”- The app generates a random
code_verifierand derives acode_challenge(SHA-256 hash, base64url-encoded). - The app redirects the browser to Keycloak’s authorization endpoint with:
response_type=code,client_id,redirect_uri,scope=openid,code_challenge,code_challenge_method=S256, and astateparameter. - Keycloak shows the login page. The user enters credentials and authenticates.
- Keycloak redirects back to the app’s
redirect_uriwith an authorizationcode(and thestatevalue for CSRF protection). - The app sends the
codeandcode_verifierto Keycloak’s token endpoint (POST /token) to exchange for tokens. - Keycloak verifies that
SHA-256(code_verifier)matches thecode_challengesent earlier. If they match, it returns theaccess_token,id_token, andrefresh_token.
Token exchange (step 5)
Section titled “Token exchange (step 5)”The curl below shows the token exchange at step 5. Fill in your values before running.
curl -X POST https://${KC_URL}/realms/${REALM}/protocol/openid-connect/token \
-d "grant_type=authorization_code" \
-d "client_id=${CLIENT_ID}" \
-d "code=${AUTH_CODE}" \
-d "redirect_uri=${REDIRECT_URI}" \
-d "code_verifier=${CODE_VERIFIER}"Trade-offs
Section titled “Trade-offs”| Option | Benefit | Cost |
|---|---|---|
| Authorization Code + PKCE | The token never passes through the browser URL — safe for public clients, and the current standard for SPAs and mobile apps | Requires an extra round-trip to the token endpoint and handling code_verifier/code_challenge in your code |
| Implicit flow (deprecated) | Historically simpler to set up, with no separate token endpoint call | The token ends up in the URL fragment, risking exposure via browser history, referrer headers, and logs — removed from OAuth 2.1 |
| Client Credentials | Good for machine-to-machine, with no user involved | Unusable if the flow needs a real user, because there is no user context |
Common mistakes
Section titled “Common mistakes”- Using the Implicit flow in a new project from 2024 onward — the Implicit flow is deprecated because the access token comes back through the URL fragment, which can leak via browser history or referrer headers. Every new project should use Authorization Code + PKCE instead, for both SPAs and mobile apps.
- Skipping the
stateparameter —stateprevents CSRF by having the app generate a random value before redirecting, then checking that the same value comes back on callback. Skipping it lets an attacker trick a user into logging in under the attacker’s own session. - Putting a client secret in an SPA or mobile app — a public client cannot store a secret securely, because client-side code can always be inspected. This is exactly why PKCE exists — it replaces the secret with a
code_verifiergenerated fresh every time. - Not validating
code_challenge_method— onlyS256should be accepted; never allow a fallback toplain, which defeats the purpose of PKCE entirely.
Real-world example
The “Log in with Google/GitHub” button — a live, production example of Authorization Code + PKCE in use today; nearly every web and mobile app uses this exact pattern.
Mobile banking apps — also use Authorization Code + PKCE for native login screens, because a mobile app counts as a public client that cannot securely hold a client secret, so it relies on PKCE to prevent the authorization code from being intercepted during the redirect.