realmlint

Keycloak fixes

Keycloak fix · medium

Securing Keycloak public clients: require PKCE and turn off the implicit flow

Single-page and mobile apps cannot keep a client secret. Here is how to protect them in Keycloak with PKCE, and which old flows to turn off.

realmlint check pkce-not-enforcedKeycloak 26Updated

Browser apps and mobile apps are public clients: anything shipped to a user's device can be read, so they cannot keep a client secret. Keycloak protects their logins with PKCE, but only requires it if you tell it to. Older setups also tend to have the implicit flow and password grant switched on.

What realmlint reports

  MEDIUM    Public client does not require PKCE [pkce-not-enforced]
            - client "web-spa": PKCE is not required
  MEDIUM    Implicit flow is enabled [implicit-flow-enabled]
            - client "web-spa": implicit flow is enabled
  MEDIUM    Password grant is enabled [direct-access-grants]
            - client "web-spa": direct access grants are enabled on a public client

Keycloak's own clients, such as account-console and admin-cli, are not reported. Their defaults are Keycloak's to manage.

Why it matters

Fix it in the admin console

  1. Open the realm and go to Clients, then select the browser or mobile client.
  2. On the Settings tab, find Capability config.
  3. Check that Client authentication is off. That is what makes it a public client.
  4. Keep Standard flow on, and turn off Implicit flow and Direct access grants.
  5. Turn on Require PKCE, then click Save.

In Keycloak releases before 26.6 the PKCE setting may be on the client's Advanced tab instead. Set it to S256, never plain.

Make sure the app sends PKCE

Once PKCE is required, Keycloak rejects logins that do not include a code challenge, so check the app before you save in production. Current OIDC libraries for browsers and mobile, including keycloak-js, oidc-client-ts and AppAuth, support PKCE with S256. In keycloak-js, pass pkceMethod: "S256" to init() if your version does not use it by default. If the app relied on the implicit flow, switch it to the authorization code flow at the same time.

Fix it in a realm file

{
  "clientId": "web-spa",
  "publicClient": true,
  "standardFlowEnabled": true,
  "implicitFlowEnabled": false,
  "directAccessGrantsEnabled": false,
  "attributes": {
    "pkce.code.challenge.method": "S256"
  }
}

Check it worked

kc.sh export --realm myrealm --file myrealm.json
realmlint check myrealm.json

The three findings should be gone for that client. Then log in to the app once to confirm it sends PKCE.