Skip to main content

Get Keycloak Auth Access Token

Understanding Keycloak Auth Access Token – A Deep Dive
$ keycloak / auth-deep-dive

🔐 Identity & Access Management

Getting a Keycloak Access Token
— and Actually Understanding It

June 2025 Keycloak OAuth 2.0 JWT OpenID Connect Service Account

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:

HTTP Token Endpoint
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.

ℹ️
Client Credentials vs Password Grant Use 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:

access_token
A signed JWT Bearer token. This is what you send to your APIs in the Authorization: Bearer … header. It encodes roles, scopes, and expiry directly inside it — no database lookup needed.
expires_in
60 seconds. The access token is valid for only 1 minute. Short-lived by design — if it leaks, damage is limited. Your client must refresh or re-fetch before it expires.
refresh_expires_in
0 — no refresh token issued. Client Credentials grants don't get refresh tokens in Keycloak. Re-authenticate when the token expires.
token_type
Bearer — the standard OAuth 2.0 token type. "Bearer" means whoever holds the token can use it.
id_token
An OpenID Connect identity token, issued because the scope included openid. Contains identity info about the authenticated principal (the service account here). Not for API authorization — for identity assertion only.
not-before-policy
0 — no minimum-issued-at policy set. Keycloak won't reject tokens issued before a certain time.
scope
openid email profile — the set of claims the token is allowed to carry. Governed by the client's configured scopes in Keycloak.

🧬 Step 3 — Inside the JWT (Three Parts)

A JWT is three Base64URL-encoded segments joined by dots: Header . Payload . Signature.

HEADER (red) eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJubm5pQjRyeEwwcWhHNzlwa0YzY2NRal9sMGpEbXhLcllZeTJZTGxFZTFzIn0
.
PAYLOAD (purple) eyJleHAiOjE3ODEzNDkwMDAsImlhdCI6MTc4MTM0ODk0MCwianRpIjoidH…
.
SIGNATURE (green) GJxSWj3QLAYgFEJ1PFZhjz21-8QluiE1Kpe…

Decoded Header

JSONHeader (algorithm + key id)
{
  "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

JSONPayload (decoded)
{
  "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:

HTTPCalling a protected API
GET https://api.eazybank.internal/v1/accounts
Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
    
JAVASpring Boot — auto-validate with Keycloak adapter
// 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

1
Service sends Client Credentials request
POST to Keycloak with client_id + client_secret (or mTLS in production).
POST /realms/master/protocol/openid-connect/token
2
Keycloak validates credentials
Checks client secret, looks up service account, evaluates realm/client roles.
Signs JWT with RS256 private key (kid: nnniBrx…)
3
Returns token response (60s window)
access_token + id_token + metadata. Cache the token; re-fetch ~5s before expiry.
expires_in: 60
4
Service calls downstream API with Bearer token
Downstream validates JWT locally — no Keycloak call needed per request.
Authorization: Bearer eyJhbG…
5
Resource server verifies signature via JWKS
Fetches Keycloak's public key once (cached). Checks sig, exp, iss, aud claims.
GET /realms/master/protocol/openid-connect/certs

🛡️ Step 7 — Security Checklist

⚠️
Tighten allowed-origins in production The token shows "allowed-origins": ["/*"]. That's fine for local dev, but restrict to specific domains before going live.
⏱️
Short expiry (60s)
Already set tightly. Leaked tokens expire fast. Cache and refresh proactively.
🔑
RS256 (asymmetric)
Private key signs; public key verifies. Resource servers never need the secret.
🚫
No refresh token
Client Credentials grants can't rotate silently. Re-authenticate on expiry.
✅
Validate on every request
Check exp, iss, aud, and signature. Don't trust a token just because it arrived.
🔒
Least privilege roles
Remove unused realm roles. This client has offline_access but probably doesn't need it.
🌐
Use HTTPS in prod
127.0.0.1:7080 is dev only. TLS everywhere when exposed externally.
✅
Summary: what to remember The access_token is a self-contained RS256-signed JWT. It carries roles, expiry, and identity directly in its payload. Your resource server validates it offline using Keycloak's public JWKS — fast, scalable, no per-request auth calls. The 60-second window keeps things tight; just cache and re-fetch on expiry.

Written for developers building with Keycloak + Spring Boot / microservices. Token values shown are from a real local dev environment and contain no sensitive data.

Verify tokens yourself at jwt.io  ·  Keycloak docs at keycloak.org/docs

Comments

Popular posts from this blog

Observability & Monitoring through Loki,Promtail (Alloy),Prometheus,Micrometer in Grafana

🔧 What this demo covers End-to-end observability setup using Prometheus + Loki + Grafana Integration of Micrometer with Spring Boot for real-time metrics Log collection using Promtail / Alloy from application containers 📊 Metrics Monitoring (Prometheus) Scraping metrics from /actuator/prometheus endpoint JVM metrics: memory, threads, GC activity HTTP metrics: request count, latency, error rates Custom metrics via Micrometer 📜 Centralized Logging (Loki + Promtail) Aggregates logs from multiple microservices Label-based log filtering (fast & efficient) No heavy indexing → lightweight compared to ELK 📈 Visualization (Grafana Dashboards) Real-time dashboards for metrics & logs Correlate logs with metrics for faster debugging Pre-built + custom dashboards ⚙️ Architecture Flow Spring Boot app → exposes metrics via Micrometer Prometheus → scrapes & stores metrics Promtail/Alloy → collects logs → pushes to Loki...

Setting Alerts , Notification Policies , Contact Points in Grafana

Grafana · Alerting Setting Up Alert Policies, Contact Points & Notification Policies in Grafana Published May 23, 2026  ·  14 min read  ·  Grafana 10+ A complete step-by-step guide to configuring Grafana's three pillars of alerting — alert rules, contact points, and notification policies — so your team gets the right alert, on the right channel, at the right time. Table of Contents 1. How Grafana Alerting Works 2. Prerequisites & Enabling Unified Alerting 3. Setting Up Contact Points 4. Configuring Email Contact Points 5. Configuring Webhook Contact Points 6. Other Contact Point Types 7. Creating Alert Rules (Alert Policies) 8. Setting Up Notification Policies 9. Mute Timings & Silences 10. Testing & Troubleshooting 11. Best Practices 1. How Grafana Alerting Works Grafana Unified Alerting has three core bui...