OAuth2 and OpenID Connect, with Keycloak

How Prayog knows who is calling: a person in the web terminal or a bot running on someone's laptop. The gateway never sees a password. It checks a signed token issued by Keycloak. This page explains the flows, PKCE, what is inside a token and how the gateway will validate it.

1. The problem

OAuth2 and OpenID Connect are the standard answer: a dedicated identity server (Keycloak) authenticates callers and issues short-lived, signed tokens that any service can verify on its own.

2. A business example: the hotel key card

3. The four roles, and OAuth2 vs OIDC

RoleIn Prayog
Resource ownerThe trader (or, for a bot, the bot's owner)
ClientThe web terminal (prayog-web), a bot (prayog-bot-demo), the simulated traders (prayog-agents)
Authorization serverKeycloak, realm prayog
Resource serverThe gateway (prayog-api), from S10

4. Flows (grant types)

FlowForStatus
Authorization code + PKCEApps with a user: browser apps, mobile, CLI with a browserThe recommended flow for any user sign-in
Client credentialsMachine to machine: bots, servicesStandard; the client authenticates as itself
Refresh tokenGetting a new access token without signing in againUsed with authorization code
ImplicitOld browser apps (token in the URL)Deprecated: tokens leak through history and logs
Resource owner passwordThe app collects the user's password itselfDeprecated: defeats the point of a separate identity server

Prayog's realm enables exactly two: authorization code + PKCE for prayog-web, client credentials for the bots. Everything else is switched off per client.

5. PKCE, step by step

A browser app is a public client: its code is downloaded by everyone, so it can't keep a secret. Without one, anyone who intercepts the one-time authorization code (a malicious browser extension, a leaked redirect) could swap it for tokens. PKCE (Proof Key for Code Exchange, RFC 7636) fixes that:

  1. The app makes a random code verifier and keeps it in memory.
  2. It sends the user to Keycloak with only the code challenge = base64url(SHA-256(verifier)).
  3. The user signs in; Keycloak redirects back with an authorization code and remembers the challenge.
  4. The app exchanges the code for tokens, sending the verifier. Keycloak hashes it and compares it with the challenge.
  5. A thief with only the code can't produce the verifier, because the hash can't be reversed.

6. Tokens: JSON Web Tokens

Keycloak's access tokens are JWTs: three base64url parts joined by dots, header.payload.signature.

PartHolds
HeaderAlgorithm (RS256: RSA signature with SHA-256) and kid, which of Keycloak's keys signed it
PayloadClaims: iss issuer, sub subject, aud audience, exp/iat expiry and issue time, azp the client that asked, realm_access.roles
SignatureKeycloak's private-key signature over header and payload. Anyone can read a JWT; only Keycloak can make one.

The gateway validates every token locally, with no call to Keycloak per request:

  1. Fetch Keycloak's public keys once from the JWKS endpoint (listed in the discovery document) and cache them.
  2. Check the signature with the key named by kid.
  3. Check iss is exactly http://auth.prayog.localhost/realms/prayog.
  4. Check aud contains prayog-api, so a token minted for some other app is refused.
  5. Check exp is in the future (with a small allowance for clock skew).
  6. Map roles (trader, ops, bot) to permissions, and sub to a Prayog account.
A JWT's payload is encoded, not encrypted. Never put secrets in claims, and never trust a token you haven't checked the signature of.

7. Decode a token

Get a real one from your running stack:

source .env && curl -s -d grant_type=client_credentials -d client_id=prayog-bot-demo \
  -d client_secret="$PRAYOG_BOT_DEMO_SECRET" \
  http://auth.prayog.localhost/realms/prayog/protocol/openid-connect/token | python3 -c 'import json,sys; print(json.load(sys.stdin)["access_token"])'

Paste it below. Decoding happens in your browser only; the page sends nothing anywhere. It checks the claims the gateway will check (not the signature, which needs Keycloak's public key).

8. Keycloak's terms

TermMeaning
RealmAn isolated set of users, clients and roles. master is for Keycloak's own admins; Prayog uses prayog.
ClientAn application registered with the realm: its allowed flows, redirect URIs and (if confidential) its secret
Public / confidentialCan't / can keep a secret. Browser apps are public; bots and servers are confidential.
Service accountThe user that a confidential client acts as in client credentials. Prayog gives bot service accounts the bot role.
Realm roleA role valid across the realm (trader, ops, bot), placed in realm_access.roles
Protocol mapperAdds or shapes claims. Prayog's audience mapper adds prayog-api to aud.
Realm importA JSON file loaded at start-up. Prayog's has ${...} placeholders filled from .env, and is imported only if the realm doesn't exist yet.

9. In Prayog

ClientTypeFlowWho
prayog-webPublicAuthorization code + PKCE (S256), redirects only to app.prayog.localhostPeople in the web terminal (S18)
prayog-apiNo flowsNone: it's the audienceThe gateway (S10)
prayog-bot-demoConfidentialClient credentialsThe sample bot (S12); "API key" = client id + secret
prayog-agentsConfidentialClient credentialsSimulated traders (S13)

make smoke already proves the important parts: the issuer, a client-credentials token with aud: prayog-api and role bot, a 401 for a wrong secret, the users' roles and PKCE on the web client.

10. Questions

If tokens are checked locally, how do you log someone out or block a bot?

Keep access tokens short-lived (Prayog: 5 minutes). Disabling the user or client in Keycloak stops new tokens; the old one expires soon after. For an instant stop, Prayog also has its own kill switch: ops can disable the account inside the exchange (S6).

Why not simple API keys for bots?

A client id + secret is an API key, but exchanged for a short-lived token. The secret travels only to Keycloak, and the gateway checks one kind of credential for humans and bots alike.

Where should a browser app keep tokens?

In memory, ideally, with a refresh token handled carefully. Local storage is readable by any script on the page, so a single XSS bug would leak tokens. Prayog's web app (S18) uses oidc-client-ts, which follows these practices.