Skip to content

Authorization Code Flow + 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 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.

  1. The app generates a random code_verifier and derives a code_challenge (SHA-256 hash, base64url-encoded).
  2. 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 a state parameter.
  3. Keycloak shows the login page. The user enters credentials and authenticates.
  4. Keycloak redirects back to the app’s redirect_uri with an authorization code (and the state value for CSRF protection).
  5. The app sends the code and code_verifier to Keycloak’s token endpoint (POST /token) to exchange for tokens.
  6. Keycloak verifies that SHA-256(code_verifier) matches the code_challenge sent earlier. If they match, it returns the access_token, id_token, and refresh_token.

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}"
OptionBenefitCost
Authorization Code + PKCEThe token never passes through the browser URL — safe for public clients, and the current standard for SPAs and mobile appsRequires 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 callThe token ends up in the URL fragment, risking exposure via browser history, referrer headers, and logs — removed from OAuth 2.1
Client CredentialsGood for machine-to-machine, with no user involvedUnusable if the flow needs a real user, because there is no user context
  • 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 state parameter — state prevents 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_verifier generated fresh every time.
  • Not validating code_challenge_method — only S256 should be accepted; never allow a fallback to plain, 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.

Why is PKCE needed for public clients?
What does the app send with the authorization request to enable PKCE?
What does Keycloak verify when the code is exchanged at the token endpoint?
Which flow is deprecated and should NOT be used?