Authentication is a topic that developers don't like to touch. Too specific, too risky, too boring. The result: every service has its own auth logic, often copied from tutorials, in varying degrees of maturity. Anyone who has ever debugged auth bugs in production knows how expensive this approach is.
Why Keycloak
Keycloak is not a shiny new thing, but a mature project (now 16 years old) that has established itself in enterprise contexts. It speaks standards-compliant protocols: OIDC, SAML, Token Exchange. It runs in Kubernetes. It can be administratively controlled via UI, REST or Admin CLI.
There are alternatives: Authentik, Ory, or commercially Auth0. We deliberately opted for the more mature, albeit somewhat heavier Keycloak - for the simple reason that our customers in regulated industries often explicitly ask for Keycloak compatibility.
Realms and clients: the model we need
We have a separate realm for each tenant. This is important because Keycloak realms are true isolation boundaries - a compromise in one realm does not harm others. All clients (front-end SPAs, back-end services, machine-to-machine accounts) and all users live within a realm.
The user source is configured per realm. Some tenants have an LDAP sync, others an Azure AD provider, others a native user database. The application layer sees no difference - it receives an OIDC token and trusts it.
No shared secrets
A common anti-pattern: Services authenticate with each other with a shared secret that lives in some environment variable. This does not scale. A leak means rotation for all services. A new service means access to secret management.
The clean alternative is Service Account Tokens: Each service has a keycloak client with its own secret, gets a token, and sends it on the API call. The receiving service verifies the token against Keycloak (or via JWKS cache). The token is short-lived (5 minutes) and can be rotated without a major rollout.
PKCE for SPAs
For front-end applications, we have replaced the classic Implicit Flow and Code Flow options in favour of Authorization Code Flow + PKCE. The difference: no more client secrets in the browser, no more tokens in the URL fragment. Instead, the client generates a cryptographic proof-of-possession, which Keycloak validates against the initial request.
Keycloak has supported PKCE natively for several years. The front-end library oidc-client-ts plays along perfectly. The migration effort was 2 person-days per SPA - manageable.
Lessons learned
- Run token caching aggressively ** JWKS endpoints on Keycloak are cheap, but with 10k+ requests/min it gets painful. In-memory caching with 1h TTL is a good compromise.
- Consciously choose session timeouts Our initial default - 30 minutes access token, 8h refresh token - was too generous. We have reduced this to 15min/4h for administrative tools.
- Structure logs cleanly ** Keycloak produces a lot of noise by default. A targeted logback filter on the events we are interested in (login errors, token rotations, admin actions) has made our Grafana dashboards usable.
Outlook
The next topic is delegated access - user A may grant user B limited rights (e.g. "broker sees his customer's policies"). Keycloak has the UMA protocol for this, but it has proven to be difficult to access in practice. We are currently evaluating whether we should rely on Keycloak Authorization Services or implement delegation in a separate service layer.