🔐 Identity & Access Management
Getting a Keycloak Access Token
— and Actually Understanding It
You hit Keycloak's token endpoint, you get back a fat JSON blob — but what is all that stuff? This post dismantles a real token response piece by piece so you know exactly what you have, why the JWT is structured the way it is, and what to do with it next.
Step 1 — How to Get the Token
Keycloak speaks OAuth 2.0. The endpoint that issues tokens lives under your realm:
POST http://127.0.0.1:7080/realms/master/protocol/openid-connect/token Content-Type: application/x-www-form-urlencoded grant_type=client_credentials &client_id=eazybank-callcenter-cc &client_secret=<YOUR_CLIENT_SECRET>
This uses the Client Credentials Grant — the right flow when a machine (service account)
authenticates on its own behalf, with no human user involved.
The client_id here is
eazybank-callcenter-cc, and Keycloak issues a service account token for it.
grant_type=client_credentials for backend services.
Use grant_type=password only for user-facing flows
(and even then, prefer Authorization Code + PKCE).
Step 2 — The Full Token Response, Decoded
Keycloak returns a JSON object. Here's what each field actually means:
Authorization: Bearer … header. It encodes roles, scopes, and expiry directly inside it — no database lookup needed.openid. Contains identity info about the authenticated principal (the service account here). Not for API authorization — for identity assertion only.Step 3 — Inside the JWT (Three Parts)
A JWT is three Base64URL-encoded segments joined by dots: Header . Payload . Signature.
Decoded Header
{
"alg": "RS256", // RSA + SHA-256 signing
"typ": "JWT",
"kid": "nnniBrxL0qhG79pkF3ccQj_l0jDmxKrYYy2YLlEe1s" // key ID — used to look up the public key
}Decoded Access Token Payload — All Claims Explained
{
"exp": 1781349000, // Expiry (Unix timestamp) — token dies here
"iat": 1781348940, // Issued-at — 60s before exp (matches expires_in: 60)
"jti": "trtcc:312ee6b5-...", // JWT ID — unique per token, for replay prevention
"iss": "http://127.0.0.1:7080/realms/master", // Issuer — your Keycloak realm URL
"aud": "account", // Audience — intended recipient service
"sub": "47ea3794-558d-423c-934e-ea8c4d341805", // Subject — Keycloak UUID of the service account
"typ": "Bearer",
"azp": "eazybank-callcenter-cc", // Authorized party — the client that requested the token
"acr": "1", // Authentication context class — level 1 (password equiv)
"allowed-origins": ["/*"], // CORS: all origins allowed (tighten in prod!)
"realm_access": {
"roles": [
"default-roles-master",
"offline_access",
"uma_authorization"
]
},
"resource_access": {
"account": {
"roles": ["manage-account", "manage-account-links", "view-profile"]
}
},
"scope": "openid email profile",
"clientHost": "172.17.0.1", // Docker gateway IP of the requesting client
"email_verified": false,
"preferred_username": "service-account-eazybank-callcenter-cc",
"clientAddress": "172.17.0.1",
"client_id": "eazybank-callcenter-cc"
}Step 4 — Claims Quick Reference
| Claim | Value (this response) | What it means |
|---|---|---|
| exp | 1781349000 | Token expiry as Unix epoch. Always validate this server-side. |
| iat | 1781348940 | Issued-at time. exp - iat = 60s matches expires_in. |
| iss | …/realms/master | Your API must verify this matches the expected Keycloak realm. |
| sub | 47ea3794-… | Keycloak's UUID for this service account principal. |
| azp | eazybank-callcenter-cc | The OAuth client that requested this token. |
| realm_access.roles | ["default-roles-master", …] | Realm-wide roles assigned to this client/user. Used for authorization decisions. |
| resource_access | account → manage-account, … | Per-client roles. Lets fine-grained API permissions live on specific resources. |
| preferred_username | service-account-eazybank-… | Auto-generated service account username. Confirms this is machine-to-machine auth. |
| alg (header) | RS256 | Asymmetric signing. Verify using Keycloak's public key — never the secret. |
Step 5 — Using the Token in API Calls
Place the access_token in the HTTP Authorization header:
GET https://api.eazybank.internal/v1/accounts Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9... Content-Type: application/json
// application.yml spring: security: oauth2: resourceserver: jwt: issuer-uri: http://127.0.0.1:7080/realms/master # Spring fetches Keycloak's JWKS, validates RS256 signature, # checks exp/iss automatically — zero extra code.
Step 6 — The Full Auth Flow
client_id + client_secret (or mTLS in production).Step 7 — Security Checklist
"allowed-origins": ["/*"].
That's fine for local dev, but restrict to specific domains before going live.
Comments
Post a Comment