Keycloak Public vs Confidential Clients: Which Should You Use?

Tested on Red Hat Enterprise Linux 10.2 (Coughlan)
Package quay.io/keycloak/keycloak:26.7.3
Applies to Any Keycloak 26.x deployment — choosing and configuring OIDC public vs confidential clients
Privilege Keycloak admin access in the target realm — bootstrap admin on the lab container is enough
Lab environment Keycloak 26.7.3 on http://127.0.0.1:8080/ with golinuxcloud realm — Install Keycloak with Podman
Scope Public vs confidential decision, Client authentication ON/OFF (replacing Access Type), architecture choice table, how to configure each type, and the consequences (PKCE for public clients, client_credentials for confidential). Not a full PKCE, redirect-URI, or service-account role-mapping tutorial.
Related guides Keycloak clients explained
Keycloak service accounts
Keycloak realm roles vs client roles
Short on time?Beta Get the gist of this 9-minute read in seconds, then ask follow-up questions

In this article we will compare public and confidential OIDC clients in Keycloak 26.7.3 so you can pick the right type for each application. The difference is not that one client type is inherently more secure than the other. It depends on whether the application can safely keep a credential such as a client secret.

Public clients (browser SPAs, installed mobile apps) cannot keep a client secret. Confidential clients (server-side web apps, backend services that obtain tokens, batch jobs) can. In the current Admin Console that maps directly to Client authentication OFF or ON.

If you need client vocabulary first, read Keycloak clients explained before this page.


Public vs confidential clients at a glance

Public client Confidential client
Can keep a client secret? No — anything in the browser or mobile binary is exposed Yes — the credential remains on a server or secrets manager you control
Client authentication OFF ON
Typical apps SPA, mobile app, desktop app with embedded login Server-side web application, backend service that obtains tokens, batch/daemon application
Machine-to-machine (client_credentials) Not allowed Supported when service accounts are enabled

A public client still authenticates users. User login is separate from client authentication. Confidential means the client application itself can authenticate to Keycloak during token requests.


Public vs confidential clients in the Keycloak Admin Console

Client authentication OFF means a public client. Client authentication ON means a confidential client.

Keycloak 26.x replaces the old Access Type dropdown with Client authentication on Clients → client → Settings → Capability config.

Admin Console today Client type
Client authentication OFF Public client
Client authentication ON Confidential client

Older tutorials may tell you to choose Access Type public or confidential. In the current Admin Console, use Client authentication OFF or ON instead.

Old setting Current interpretation
public Client authentication OFF
confidential Client authentication ON
bearer-only Deprecated; for a resource-server-only client, configure an OIDC client with no grant flows enabled

Keycloak 26.7 documents that the old Bearer-only switch is deprecated and will be removed. Prefer an OIDC client that does not enable login grants when the application only represents a service or resource server.


Which client type should you use?

Application Keycloak client type
Browser SPA Public
Native / mobile app Public
Desktop or native CLI with user login Usually public
Server-rendered web app Confidential
Backend service that obtains its own token Confidential
Batch / cron process Confidential
Backend-for-frontend (BFF) Confidential; the BFF handles OIDC server-side, so the browser typically does not act as a Keycloak client directly
API that only validates bearer tokens Does not need a client secret merely because it is an API

JavaScript bundles, mobile APKs, and desktop binaries ship to the user's device. Any static string you embed, including a so-called hidden client secret, can be extracted. Obfuscation does not make a browser app confidential.

Native mobile apps are public for the same reason: credentials embedded in the installed application cannot be treated as secret. If you use a backend-for-frontend pattern, the BFF can be a confidential client because its credential remains on the server. The browser normally talks to the BFF using the application's session rather than storing an OAuth client secret itself.

A resource API that only validates incoming access tokens is a separate case. Being a backend API alone does not automatically mean it needs client credentials.


How to configure a public client

For a browser SPA such as storefront-spa:

  1. Set Client authentication to OFF.
  2. Enable Standard flow.
  3. Under Capability config, set PKCE Method to S256 and enable Require PKCE when you want Keycloak to enforce it.
  4. Register exact SPA callback URLs under Valid redirect URIs. Avoid production wildcards.

Keycloak storefront-spa Capability config with Client authentication off, Standard flow on, and PKCE Method S256

Public clients cannot rely on a client secret, so redirect URIs should be as tight as possible. Redirect URI validation stops Keycloak from sending authorization responses to unregistered locations. PKCE separately stops someone who steals an authorization code from redeeming it.

Redirect URI practice Why
Exact callbacks per environment Keycloak rejects callbacks not on the list (invalid_redirect_uri)
No * wildcards in production A compromised subdomain widens the attack surface
Separate lab and prod client records when feasible Keeps localhost URIs out of production
Match scheme, host, port, and path 127.0.0.1 and localhost are different URIs

Never call token endpoints with a client secret from frontend code.


How to configure a confidential client

For a server-side application such as storefront-web:

  1. Set Client authentication to ON.
  2. Enable Standard flow when users log in through this client.
  3. Open Credentials and configure a client authenticator.
  4. Enable Service account roles only when the application needs the client_credentials grant.

Keycloak storefront-web Capability config with Client authentication on

The OAuth credential lives in environment variables, a secrets manager, or the container platform, not in HTML sent to the browser. When Client authentication is ON, Keycloak needs proof that the caller is the registered application:

Method Typical use
Client secret Default; server apps read from env or secrets manager
Signed JWT / private key Signed assertion instead of a shared secret
X.509 / mTLS Certificate-based client auth in hardened environments

Keycloak confidential client Credentials tab showing generated client secret

Rotate the secret if it leaks and update every server instance that reads it. The value never belongs in frontend repositories or mobile app config checked into git.

User authentication (username, password, MFA) is unrelated. That is the human logging in, not the OAuth client proving its identity.


Why public clients use PKCE

Public clients have no shared secret to present at the token endpoint, so they should use the authorization code flow with PKCE (Proof Key for Code Exchange), not the legacy implicit flow.

PKCE binds the authorize request to the later code exchange:

  1. The client creates a random code_verifier and keeps it locally.
  2. It sends a code_challenge (usually the S256 hash of the verifier) on the authorize request.
  3. When exchanging the authorization code for tokens, it sends the original code_verifier. Keycloak checks that it matches the earlier challenge.

An attacker who intercepts only the authorization code cannot redeem it without the verifier.

On Keycloak 26.7.3, set this under Settings → Capability config → Require PKCE and PKCE Method → S256:

Keycloak Capability config showing Require PKCE and PKCE Method set to S256

Mechanism Purpose
Client authentication Proves to Keycloak that the application making a back-channel request is the registered confidential client
PKCE Proves that the application exchanging an authorization code is the same one that started the login request

They are complementary. PKCE does not create a client identity, encrypt tokens, or make a public client confidential. A confidential server app may also use PKCE. A public SPA with PKCE remains public: there is still no client secret, and client_credentials stays unavailable.


Can a public Keycloak client use client_credentials?

No. In Keycloak, client_credentials requires a client that can authenticate itself and has Service account roles enabled. That is how a backend service obtains a token without a user logging in.

Keycloak storefront-web Capability config with Client authentication on and Service account roles enabled

To use client_credentials:

  • Client authentication ON
  • Service account roles ON
  • A configured client authenticator such as the generated secret

Then assign the service-account roles or scopes the application needs. Role assignment is not required merely to obtain a token; it controls what permissions and role claims the token carries. Public clients cannot use service accounts. For role mapping and token claims, see Keycloak service accounts.

A confidential client with service accounts enabled can call the token endpoint like this:

bash
curl -s -X POST 'http://127.0.0.1:8080/realms/golinuxcloud/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' -d 'client_id=storefront-web' -d 'client_secret=YOUR_CLIENT_SECRET'

Replace YOUR_CLIENT_SECRET with the value from Clients → storefront-web → Credentials. Sample trimmed response:

output
{"access_token":"eyJ...","expires_in":300,"token_type":"Bearer","scope":"profile email"}

The access token azp claim is storefront-web. The client authenticated as itself, not as an end user.

The same grant against public storefront-spa fails:

bash
curl -s -X POST 'http://127.0.0.1:8080/realms/golinuxcloud/protocol/openid-connect/token' \
  -H 'Content-Type: application/x-www-form-urlencoded' \
  -d 'grant_type=client_credentials' -d 'client_id=storefront-spa'

Sample output:

output
{"error":"unauthorized_client","error_description":"Public client not allowed to retrieve service account"}

A public client cannot use the service-account client_credentials flow. For backend jobs or machine-to-machine calls, create a confidential client and enable service accounts.


Common mistakes

Mistake Why it fails Fix
Client secret in React or Angular source Secret is public to every visitor Public client + PKCE; keep secrets on the server
Public client for cron job / API integration client_credentials blocked Confidential client + service accounts
Assuming PKCE encrypts access tokens PKCE only binds the code exchange Use HTTPS and short-lived tokens; PKCE is not encryption
* redirect URI in production Any matching path can receive codes List explicit callbacks per environment
Following old Access Type screenshots UI field names changed Map to Client authentication ON/OFF
Confusing user login with client auth User password ≠ client secret User auth is the person; client auth is the application
Disabling Standard flow but expecting browser login No authorization code path Enable Standard flow for interactive OIDC
Reusing SPA client for backend job Wrong trust model Separate confidential client per workload
Treating every backend API as confidential Token validation alone does not need client credentials Use confidential only when the app obtains tokens itself

Summary

Choose public when the app cannot keep a credential (SPA, mobile). Choose confidential when a server you control can authenticate to Keycloak (server-rendered app, token-obtaining backend, batch job).

On Keycloak 26.x, Client authentication OFF is public and ON is confidential. That replaces the legacy Access Type dropdown. Public clients should use authorization code with PKCE (S256) and tight redirect URIs. Confidential clients use a secret or another authenticator, and enable service accounts only when they need client_credentials.


References


Frequently Asked Questions

Should a React or Vue SPA be a public or confidential client?

Public. Frontend code cannot protect a client secret. Register the SPA as a public client, enable authorization code flow with PKCE (S256), and keep strict redirect URIs.

What replaced Access Type in Keycloak?

Client authentication on the client Settings tab under Capability config. OFF means public; ON means confidential. The old bearer-only option is deprecated; for a resource-server-only client, configure an OIDC client with no grant flows enabled.

Can I use PKCE instead of a client secret on a server app?

You can use PKCE alongside a confidential client, but PKCE does not replace client authentication for machine clients. Server apps should stay confidential and store secrets or keys server-side.

Why does client_credentials fail on my SPA client?

The client is public. Keycloak blocks service-account token grants for public clients by design. Create a separate confidential client for backend jobs.

Is a mobile app always a public client?

An installed mobile application should be treated as a public client because a shared secret distributed with the app cannot remain confidential. If you use a BFF, the BFF can be a confidential client and the browser or app typically talks to that BFF with an application session rather than acting as a Keycloak client itself.

Finished reading?Beta Get the key takeaways in seconds, then ask follow-up questions
Deepak Prasad

R&D Engineer

Founder of GoLinuxCloud with 15+ years of expertise in Linux, Python, Go, Laravel, DevOps, Kubernetes, Git, Shell scripting, OpenShift, AWS, Networking, and Security. With extensive experience, he excels across development, DevOps, networking, and security, delivering robust and efficient solutions for diverse projects.

  • Go (programming language)
  • Python (programming language)
  • DevOps
  • Computer Security
  • Cloud Computing
  • Kubernetes
  • Linux
  • Ansible (software)