PKCE in Keycloak: Locking the Front Door of OAuth
A complete, beginner-friendly walkthrough of Proof Key for Code Exchange (PKCE) — what it is, why Keycloak needs it, how it works internally, and how to use it safely in real applications.
Imagine handing your house key to a courier so they can deliver a package inside, but instead of a physical key, you hand them a claim ticket that only works once, only for that one delivery, and only if they can prove they are the same courier who picked it up in the first place. That, in a nutshell, is what PKCE (pronounced “pixy”) does for login flows. It closes a gap that used to let an attacker steal a delivery ticket meant for someone else and use it themselves. In this tutorial we will unpack PKCE piece by piece — using Keycloak, one of the most widely used open-source identity providers, as our running example.
1What Is PKCE?
Before we touch Keycloak at all, let’s build a rock-solid mental model of what PKCE actually is.
PKCE stands for Proof Key for Code Exchange. It is an extension to the OAuth 2.0 Authorization Code flow, defined in a specification called RFC 7636. Its entire job is to prove that the application asking to exchange an authorization code for tokens is the same application that originally started the login process — not an impostor who intercepted the code along the way.
Think of ordering a parcel locker pickup. When you drop off a parcel, the locker doesn’t just print a pickup code on a slip of paper anyone could grab. Instead, you set a secret PIN when you drop it off. When you (or someone) comes to collect it, the locker asks for that PIN again. Even if a stranger sees the pickup code on your receipt, they can’t open the locker without also knowing the PIN you chose. PKCE is that PIN for the OAuth login process.
To understand why this PIN is necessary, we need one more term: OAuth 2.0. OAuth 2.0 is an authorization framework — a set of rules that let one application (say, a mobile app) obtain limited access to a user’s data on another system (say, a company’s identity server) without ever seeing the user’s password. The most common way this happens is the Authorization Code flow: the app redirects the user to a login page, the user logs in, and the login server sends back a short-lived “authorization code.” The app then trades that code for access tokens and ID tokens behind the scenes.
The authorization code by itself was never meant to be a secret that stays hidden forever — it travels through a browser redirect, which is a relatively public path (think of it as being written in chalk on a sidewalk that anyone can glance at). If a malicious app or process on the same device can grab that code before the legitimate app does, it can attempt to exchange it for tokens and effectively steal the login session. PKCE was invented specifically to prevent this.
Code Verifier
A random secret string the app generates itself and keeps private, used only on this device for this one login attempt.
Code Challenge
A hashed (scrambled) version of the code verifier that the app sends up front, when starting the login.
Code Challenge Method
The hashing algorithm used to turn the verifier into the challenge — almost always SHA-256 today.
Authorization Code
The short-lived, one-time code the identity server hands back after a successful login.
Where does Keycloak come in? Keycloak is an open-source Identity and Access Management (IAM) server built by Red Hat. It plays the role of the “identity server” in the story above: it hosts login pages, manages user accounts, issues tokens, and — importantly for us — enforces PKCE rules on behalf of every application (“client,” in Keycloak’s vocabulary) that is registered with it. Keycloak supports PKCE natively; an administrator simply configures a client to require it, and Keycloak’s Authorization Endpoint and Token Endpoint handle the rest.
PKCE was originally designed for mobile and native apps, which cannot safely store a traditional “client secret.” But it has since become a best practice for nearly every kind of application — including single-page web apps and even some traditional web servers — because it adds a strong layer of protection at almost no cost.
2Why PKCE Exists: The Problem It Solves
To appreciate PKCE, we have to understand the attack it was built to stop.
In the early days of OAuth 2.0, “confidential clients” — typically server-side web applications — were given a client secret: a password-like value known only to the app’s backend server and the identity provider. When exchanging an authorization code for tokens, the app would also send this client secret to prove its identity. Since the secret never left the server, it was safe from prying eyes.
But mobile apps and single-page JavaScript applications don’t have a safe place to hide a secret. A client secret bundled inside a mobile app’s binary can be extracted with basic reverse-engineering tools. A secret embedded in JavaScript running in a browser is visible to anyone who opens the browser’s developer tools. These are called public clients — clients that structurally cannot keep a secret. Without a client secret to distinguish “trusted app” from “impostor app,” public clients were exposed to a specific attack.
Problem
On mobile operating systems, multiple apps can sometimes register the same custom URL scheme (for example, myapp://callback) to receive redirects. This is called authorization code interception. A malicious app registers the same callback scheme as a legitimate app. When the operating system redirects back after login, there is a chance the malicious app intercepts the authorization code instead of (or in addition to) the legitimate app.
Why It’s Harmful
Without PKCE, whoever holds the authorization code can exchange it for access and ID tokens, since public clients have no secret to separate the real app from the impostor. This means the attacker gains a fully valid, signed-in session as the victim, without ever seeing the victim’s password.
Correct Approach
With PKCE enabled, the malicious app has the stolen code, but not the original code verifier — a secret that never leaves the legitimate app’s memory. Keycloak’s Token Endpoint refuses to issue tokens unless the correct verifier is presented, so the stolen code becomes useless.
This same class of risk applies more broadly than just mobile deep links. Any environment where an authorization code might be observed or logged along its journey — browser history, proxy logs, referrer headers, or a compromised network — benefits from PKCE’s extra proof step. That is why the OAuth working group eventually recommended PKCE for essentially all Authorization Code flows, not only public clients.
It is worth being precise about what PKCE does not do. It does not encrypt traffic (that’s TLS’s job). It does not replace login credentials or multi-factor authentication. It does not stop every kind of attack — for example, it does nothing against a fully compromised device where an attacker can read the legitimate app’s memory. What it does extremely well is close one very specific, very common gap: proving that the party redeeming an authorization code is the same party that requested it.
3Architecture & Components in Keycloak
Let’s zoom out and see where PKCE fits inside Keycloak’s overall architecture.
Keycloak is organized around a small number of core concepts, and understanding them makes PKCE much easier to place.
Realm
An isolated space of users, clients, and settings — like a separate tenant. Each realm has its own PKCE-related client policies.
Client
An application registered with Keycloak (a mobile app, SPA, or backend service). PKCE enforcement is configured per client.
Authorization Endpoint
The URL a user’s browser is sent to in order to log in. It receives and stores the code challenge here.
Token Endpoint
The URL the app calls afterward to trade the authorization code for tokens. It verifies the code verifier here.
Inside a Keycloak client’s settings, there is a setting typically labelled something like “Proof Key for Code Exchange Code Challenge Method.” An administrator can set this to S256 (the recommended SHA-256-based method), plain (a weaker method kept mostly for legacy compatibility), or leave it unset to make PKCE optional for that client. Modern guidance strongly favors S256 and, for public clients, making PKCE mandatory rather than optional.
Public Clients
Single-page applications and mobile/native apps in Keycloak are typically registered as public clients (no client secret). For these, PKCE is effectively mandatory in any responsible deployment, because it is the only strong protection available at the authorization-code-exchange step.
Confidential Clients
Traditional server-side web applications are usually registered as confidential clients (they do have a client secret). PKCE is optional here but still recommended as defense-in-depth, since it protects against code interception even if the secret itself remains safe.
Under the hood, Keycloak’s Authorization Endpoint and Token Endpoint are just parts of a larger authentication and token-issuance pipeline that also includes authenticators (username/password forms, OTP, WebAuthn), the realm’s session store, and the token issuance logic that eventually mints signed JSON Web Tokens (JWTs). PKCE slots in as a validation step that happens strictly between the authorization request and the token exchange — it does not touch how the user actually authenticates (their password, OTP, biometrics, and so on remain unaffected).
graph TD
A[Client App] -->|1: Authorization Request + code_challenge| B[Keycloak Authorization Endpoint]
B -->|2: Redirect with login page| C[User Browser]
C -->|3: User logs in| B
B -->|4: Redirect back with authorization code| A
A -->|5: Token Request + code_verifier| D[Keycloak Token Endpoint]
D -->|6: Verify verifier against stored challenge| D
D -->|7: Access Token + ID Token| A
Notice that the code challenge is submitted at step 1 and the code verifier is submitted at step 5 — two completely separate HTTP requests, potentially seconds or minutes apart, and possibly even from different network paths. Keycloak’s job is to remember the challenge tied to that specific authorization code, and later check that the verifier presented at token exchange time actually matches it.
4The PKCE Flow: Step by Step
Now let’s walk through the entire flow in careful, ordered detail.
Generate the Code Verifier
The client application generates a long, random string (43 to 128 characters) made of letters, digits, and a few safe symbols. This is the code verifier, and it is kept only in the app’s memory — never sent anywhere yet.
Derive the Code Challenge
The app runs the verifier through the SHA-256 hashing function and encodes the result using Base64-URL encoding. This scrambled, one-way output is the code challenge.
Send the Authorization Request
The app redirects the user’s browser to Keycloak’s Authorization Endpoint, including the code challenge and the chosen challenge method (S256) as query parameters, alongside the usual OAuth parameters like client ID, redirect URI, and requested scopes.
User Authenticates
Keycloak presents its login page (and, if configured, multi-factor prompts). The user proves who they are. Keycloak stores the received code challenge alongside the session tied to this login attempt.
Receive the Authorization Code
After a successful login, Keycloak redirects the browser back to the app’s registered redirect URI, attaching a short-lived, one-time authorization code as a query parameter.
Exchange the Code — With the Verifier
The app makes a direct, back-channel request to Keycloak’s Token Endpoint, sending the authorization code and, critically, the original plain-text code verifier from step 1.
Keycloak Verifies the Match
Keycloak takes the incoming verifier, hashes it the same way (SHA-256 + Base64-URL), and compares the result against the code challenge it stored back in step 4. If, and only if, they match, the exchange proceeds.
Tokens Are Issued
Keycloak issues an access token, an ID token, and (if configured) a refresh token back to the app. The authorization code and code challenge are now discarded and can never be reused.
sequenceDiagram
participant App as Client App
participant Browser
participant KC as Keycloak
App->>App: Generate code_verifier
App->>App: Derive code_challenge = SHA256(code_verifier)
App->>Browser: Redirect to Authorization Endpoint (code_challenge, method=S256)
Browser->>KC: Authorization Request
KC-->>Browser: Show login page
Browser->>KC: Submit credentials
KC->>KC: Store code_challenge with session
KC-->>Browser: Redirect back with authorization_code
Browser->>App: Deliver authorization_code
App->>KC: Token Request (code, code_verifier)
KC->>KC: Hash code_verifier, compare to stored code_challenge
KC-->>App: Access Token + ID Token (if match)
Some newcomers assume the code challenge is sent again at token-exchange time. It is not. Only the code verifier is sent at that point — Keycloak recalculates the challenge itself and compares, which is exactly what makes the check trustworthy.
5Internal Working: Verifier, Challenge, and Hashing
Let’s go one level deeper into the cryptographic mechanics that make this trustworthy.
The security of PKCE rests on one simple mathematical property of cryptographic hash functions like SHA-256: they are one-way. Given an input, it’s trivial to compute the hash output. But given only the output, it is computationally infeasible to work backward and discover the original input. This is the same property that lets websites store password hashes instead of the passwords themselves.
Think of a hash function like a woodchipper for numbers. You can feed a plank of wood in and get a pile of sawdust out — that’s fast and easy. But no matter how carefully you examine the sawdust, you cannot reconstruct the exact original plank from it. The code challenge is the sawdust; the code verifier is the plank. Keycloak only ever sees the sawdust up front, and later checks that a newly-presented plank produces matching sawdust.
This one-way property is exactly why sending the code challenge over a semi-public channel (the browser redirect) is safe: even if an attacker captures the code challenge, they cannot reverse it to discover the code verifier. Only the real app — which generated the verifier and kept it in private memory — can produce the matching value later.
S256 (Recommended)
code_challenge = Base64URL( SHA256( code_verifier ) ). The verifier is hashed before transmission, so it never appears anywhere in plain text on the network.
plain (Discouraged)
code_challenge = code_verifier, unmodified. This exists only for constrained legacy clients that cannot perform hashing, and provides much weaker protection since the “challenge” is just the secret itself.
If the challenge method is plain, then the code challenge sent at the start of the flow is identical to the code verifier that will be needed later. Anyone who can observe the authorization request (for example, through browser history or a referrer leak) already has everything they need — defeating the purpose of PKCE entirely. Always prefer S256 unless there is a documented, unavoidable constraint.
Keycloak’s Token Endpoint performs the verification server-side, inside its authentication/token issuance pipeline, as part of validating the token request before minting any tokens. If the client’s configuration requires PKCE and no code_verifier is supplied, or the recomputed hash doesn’t match the stored challenge, Keycloak rejects the request with an error and issues no tokens whatsoever. This rejection happens before any token signing or session promotion occurs, so a failed PKCE check has zero side effects beyond the failed attempt itself.
There is also a subtlety worth understanding: the code verifier must have enough randomness (entropy) to resist guessing. The specification calls for at least 43 characters generated by a cryptographically secure random number generator, pulled from a large enough character set. A short or predictable verifier (for example, based on a timestamp) would undermine the whole mechanism, because an attacker could simply guess or brute-force it within the authorization code’s short lifetime.
6Data Flow & Lifecycle Across the Full Grant
Let’s trace the lifecycle of every PKCE-related value from birth to disposal.
Every value involved in a PKCE exchange has a short, well-defined lifespan. Understanding this lifecycle helps explain why the mechanism is both secure and lightweight.
Code Verifier
Born in app memory at login start. Never transmitted until the token request. Discarded immediately after a successful (or failed) token exchange. Never persisted to disk in a well-built app.
Code Challenge
Derived from the verifier, sent once in the authorization request, and stored by Keycloak against the pending login session. Used exactly once, at verification time, then discarded.
Authorization Code
Minted by Keycloak after login, valid for a very short window (commonly under a minute), single-use only, and invalidated the moment it is redeemed or expires.
Access / ID Tokens
Issued only after PKCE verification succeeds. These have their own separate, typically longer lifetimes governed by realm and client token settings, independent of PKCE.
Because the code verifier lives only in the requesting app’s memory and is never written to a log, a cookie, or local storage in a properly implemented client, there is no persistent artifact for an attacker to later retrieve — even if they compromise the device after the login flow has completed. This “ephemeral by design” property is one of the quieter but important security benefits of PKCE.
graph LR
A[Verifier Created] --> B[Challenge Derived]
B --> C[Challenge Sent to Keycloak]
C --> D[Login Completed]
D --> E[Authorization Code Issued]
E --> F[Verifier Sent to Token Endpoint]
F --> G{Hash Match?}
G -->|Yes| H[Tokens Issued, Code + Challenge Discarded]
G -->|No| I[Request Rejected, No Tokens Issued]
It is also important that authorization codes bound to a code challenge cannot be redeemed without the verifier — even by the legitimate app itself — if that app crashes or restarts and loses the verifier from memory. In that scenario, the app must simply restart the login flow from scratch, generating a fresh verifier and challenge pair. This is an intentional trade-off: strict single-attempt exchange in return for strong protection against theft.
7PKCE vs. the Traditional Authorization Code Flow
A side-by-side comparison clarifies exactly what changes when PKCE is added.
| Aspect | Without PKCE | With PKCE |
|---|---|---|
| Proof of client identity | Client secret only (confidential clients) or none (public clients) | Dynamic verifier/challenge pair, works for both client types |
| Protection for public clients | Weak — no secret exists to check | Strong — verifier substitutes for a secret |
| Risk if authorization code is intercepted | High — code alone may be enough to get tokens | Low — code alone is useless without the verifier |
| Extra parameters required | None beyond standard OAuth fields | code_challenge, code_challenge_method, code_verifier |
| Suitable for mobile/native/SPA apps | Not recommended | Recommended / mandatory |
Notice that PKCE does not replace the client secret for confidential clients — it complements it. A confidential client configured for PKCE in Keycloak still sends its client secret and a code verifier at token-exchange time, giving two independent layers of proof rather than one.
Current OAuth 2.0 security best-practice guidance (including the OAuth 2.0 Security Best Current Practice document) recommends using PKCE for all Authorization Code grants, regardless of client type, precisely because it is cheap to implement and closes a whole category of interception attacks.
8Advantages, Disadvantages & Trade-offs
No mechanism is free of trade-offs. Let’s weigh PKCE honestly.
Advantages
- Neutralizes authorization code interception attacks without requiring a stored secret.
- Works identically for public and confidential clients, simplifying client-type decisions.
- Adds no extra round trip — the challenge rides along with the existing authorization request, and the verifier rides along with the existing token request.
- Requires no changes to how the end user experiences login — it is entirely invisible to them.
- Is now supported natively by virtually every OAuth/OpenID Connect library and by Keycloak out of the box.
Disadvantages / Trade-offs
- Adds a small amount of implementation complexity: generating secure random verifiers and computing SHA-256 hashes correctly.
- Provides no protection if the verifier itself is leaked (for example, through insecure logging by a careless developer).
- Does not protect against a fully compromised client device or malware capable of reading the app’s memory in real time.
- Older or poorly maintained client libraries may only support the weaker “plain” method, reducing the benefit.
In practice, the advantages heavily outweigh the costs for almost every application. The “cost” of PKCE is a few extra lines of standard library code; the benefit is closing a well-documented, historically exploited attack path. This is why Keycloak — like most modern identity providers — makes PKCE trivial to enable through a simple client configuration toggle rather than requiring custom development.
9Security Considerations
PKCE is one layer in a broader security posture. Let’s place it in context.
Transport Security (TLS)
PKCE assumes all traffic runs over HTTPS. Without TLS, an attacker on the network path could capture both the challenge and, later, the verifier, defeating the mechanism entirely.
Redirect URI Validation
Keycloak strictly validates that the redirect URI in the authorization and token requests matches a pre-registered value, preventing an attacker from redirecting the authorization code to a server they control.
State Parameter
A separate OAuth parameter used to protect against cross-site request forgery during the login redirect. PKCE and the state parameter solve different problems and are typically used together.
Short-Lived Codes
Keycloak enforces a short expiry window on authorization codes, limiting the time an attacker would have even if PKCE were somehow bypassed.
A frequent point of confusion is the relationship between PKCE and the state parameter. They are complementary, not competing. The state parameter protects the browser side of the flow — ensuring the redirect back to the app corresponds to a request the app actually initiated, preventing cross-site request forgery style attacks. PKCE protects the token exchange side — ensuring whoever redeems the code is the same party that started the flow. A well-configured Keycloak client, and a well-implemented app, uses both together.
Problem
Storing the code verifier in a location accessible to other apps or scripts on the device — for example, in a plain local storage entry on a shared browser context, or logged to a crash-reporting service.
Why It’s Harmful
The whole point of PKCE is that the verifier never leaves the requesting client’s private memory before the token exchange. If it leaks through a side channel like logs or shared storage, an attacker who also intercepts the authorization code can complete the exchange just as easily as if PKCE were absent.
Correct Approach
Keep the verifier in memory (or, if persistence across a redirect is unavoidable, in a mechanism scoped tightly to that one browser tab/session, cleared immediately after use) and never include it in logs, analytics, or crash reports.
Reusing the same code verifier across multiple login attempts. Each authorization attempt should generate a brand-new, independently random verifier. Reuse weakens the protection by making the verifier a more valuable, longer-lived target.
10Monitoring, Logging & Metrics
Enabling PKCE is only half the job — observing it in production matters too.
Keycloak exposes authentication and token events through its event logging system, which administrators typically forward to a centralized logging or SIEM (Security Information and Event Management) platform. From a PKCE standpoint, the events worth watching closely are token exchange failures, since a spike in these can be an early signal of an attempted authorization code interception attack, a misconfigured client, or a broken client-side implementation.
Failed Code Exchanges
A rising rate of token requests rejected due to PKCE mismatch may indicate either an attack attempt or a bug in a newly deployed client version.
Missing Verifier Errors
Repeated requests missing the code_verifier parameter entirely often point to an outdated client library that hasn’t been updated to support PKCE.
Unusual Client Patterns
A single client ID generating an abnormally high volume of authorization requests without matching successful logins can suggest automated probing.
Challenge Method Drift
Monitoring which challenge method (S256 vs. plain) each client actually uses helps catch clients that have silently fallen back to the weaker method.
Dashboards
Teams running Keycloak at scale commonly build dashboards that break down authentication and token-exchange success/failure rates per client, so a spike affecting one specific mobile app version (for example, after a bad release) is immediately visible rather than being buried inside an aggregate success rate.
Beyond Keycloak’s own event stream, application-side logging on the client also matters — but with a firm boundary. Applications should log that a PKCE exchange succeeded or failed, and perhaps a correlation/request ID, but should never log the verifier or challenge values themselves, since that would recreate exactly the leak risk PKCE is designed to avoid.
11Design Patterns & Anti-Patterns
Beyond the core mechanics, a few recurring patterns show up in real deployments.
Pattern: PKCE + Backend-for-Frontend (BFF)
Some architectures place a lightweight backend server between a single-page app and Keycloak. The backend performs the PKCE exchange on behalf of the browser and stores tokens server-side, issuing the browser only a session cookie. This combines PKCE’s protection with the added benefit of never exposing tokens directly to browser JavaScript.
Pattern: PKCE for Native/Mobile Deep Links
Mobile apps commonly pair PKCE with app-specific claimed HTTPS redirect URIs (rather than plain custom URL schemes) wherever the platform supports it, further reducing the chance of another app hijacking the redirect step.
Pattern: Mandatory PKCE Client Policy
Rather than relying on each client to remember to enable PKCE, administrators can define a Keycloak client policy at the realm level that requires PKCE for every client matching certain criteria (for example, all public clients), enforcing the setting centrally.
Problem
Leaving PKCE as “optional” on a public client and assuming every consumer of that client configuration will remember to implement it correctly.
Why It’s Harmful
Optional settings tend to be forgotten, especially as teams change over time or new client integrations are added quickly under deadline pressure. An optional PKCE setting provides zero protection the moment a client simply omits the parameters.
Correct Approach
Mark PKCE as required at the client level (or enforce it realm-wide via a client policy) so that Keycloak itself refuses any authorization request lacking a code challenge, removing the possibility of accidental omission.
12Best Practices & Common Mistakes
A concise checklist for teams rolling PKCE out across their Keycloak-protected applications.
Always Use S256
Avoid the “plain” challenge method unless a documented, unavoidable constraint forces it — and treat that as a temporary exception, not a permanent state.
Make PKCE Mandatory
Configure Keycloak clients — especially public ones — to require PKCE rather than merely allow it.
Generate Fresh Verifiers
Create a brand-new, cryptographically random verifier for every single login attempt — never cache or reuse one.
Combine with State & Nonce
Pair PKCE with the OAuth state parameter and, for OpenID Connect logins, the nonce parameter, for layered protection against different attack classes.
Keep Libraries Updated
Use actively maintained OAuth/OIDC client libraries, since PKCE support and correct random-generation practices are easy to get subtly wrong in hand-rolled code.
Never Log Secrets
Ensure verifiers, challenges, and authorization codes never appear in application logs, crash reports, or analytics events.
Forgetting to update the redirect URI validation alongside PKCE. PKCE protects the token exchange, but a loosely validated redirect URI can still allow other classes of attacks (like open redirects) that PKCE was never designed to address.
13Real-World & Industry Examples
PKCE is not a theoretical construct — it underpins login flows people use every day.
Mobile Banking and Fintech Apps
Financial apps frequently delegate login to an identity provider like Keycloak using the system browser or an in-app secure browser tab, with PKCE protecting the redirect back into the app — a scenario where authorization code interception risk is a well-known concern given how sensitive the resulting session is.
Single-Page Application Dashboards
Internal enterprise dashboards and admin panels built as SPAs commonly integrate with Keycloak using public clients with mandatory PKCE, since these apps have no backend server component to hold a traditional client secret.
IoT and Smart Device Companion Apps
Companion mobile apps for smart-home or industrial IoT devices often authenticate against an organization’s Keycloak instance using PKCE, since these apps are distributed publicly and cannot embed durable secrets.
Developer Tooling and CLIs
Command-line tools that need to authenticate a developer against an organization’s identity provider frequently use a “loopback” PKCE flow: a temporary local web server captures the redirect, and PKCE ensures that only the CLI process that started the flow can complete it.
Across all these examples, the common thread is the same: whenever an application cannot safely hold a long-lived secret, or whenever the authorization code might travel through an environment where interception is plausible, PKCE provides a lightweight, standardized, and now near-universal answer.
14Frequently Asked Questions
Quick answers to the questions that come up most often when teams first adopt PKCE in Keycloak.
It’s not strictly required, but it is still recommended. PKCE adds an independent layer of protection against authorization code interception that a client secret alone does not fully address, since the secret protects the token exchange call’s authenticity but not the code’s journey through the browser redirect.
Keycloak rejects the token request outright. No access token, ID token, or refresh token is issued, and the authorization code is treated as consumed, so the client must restart the entire login flow from the beginning with a fresh verifier and challenge.
No. PKCE is specific to the Authorization Code grant, which involves a user redirecting through a browser. The Client Credentials grant is a machine-to-machine flow with no user or browser involved, so there is no authorization code to protect in the first place.
It started there, but current best practice extends it to single-page applications and even traditional confidential web clients, because the protection it adds is cheap and the risk it mitigates — authorization code interception — can occur in more contexts than originally anticipated.
No. Generating a random verifier and computing a SHA-256 hash are both extremely fast local operations, and the resulting values simply piggyback on the authorization and token requests that already exist in the flow — no extra network round trips are introduced.
A client secret is a long-lived, static value shared once during client registration and reused across every login. A code verifier/challenge pair is generated fresh for every single login attempt and discarded immediately after use, making it far less valuable as a target if ever exposed.
15Summary and Key Takeaways
PKCE turns a historically vulnerable step in the OAuth 2.0 Authorization Code flow — the exchange of a code that briefly travels through a browser redirect — into a step that is provably tied to the exact application that started the login. In Keycloak, this is a simple client-level configuration choice with an outsized security benefit, protecting public clients like mobile apps and single-page applications that structurally cannot hold a durable secret, while still adding useful defense-in-depth for confidential clients that can.
Key Takeaways
- PKCE proves continuity, not identity in isolation — it confirms that whoever redeems the authorization code is the same party that started the login, using a verifier that never travels over the network until the very last step.
- Always prefer the S256 challenge method — it hashes the verifier before transmission, whereas the “plain” method offers essentially no protection.
- PKCE and client secrets are complementary, not exclusive — confidential clients benefit from using both together.
- Configure PKCE as mandatory, not optional, on Keycloak clients — especially public ones — to eliminate the risk of it being silently skipped.
- Never log or persist the code verifier — its entire value comes from staying private and short-lived in the requesting client’s memory.
- Pair PKCE with the state and nonce parameters for layered protection against different, complementary classes of attacks.
- Monitor token-exchange failure rates in Keycloak’s event logs — a sudden spike is often the first visible sign of trouble.