A Deep-Dive Visual Guide
From signed tokens and HMAC hashing, to Redis caching, OAuth 2.0 flows, and OpenID Connect — the complete picture, explained visually.
A JWT (JSON Web Token) is a compact, self-contained string that carries information between two parties in a digitally signed package. It looks like random gibberish — but it's exactly three base64-encoded JSON objects stitched together with dots.
■ Header · ■ Payload · ■ Signature
{
"alg": "HS256"
}
Tells the server which algorithm was used to create the signature, so it knows how to verify it.
{
"sub": "prashant",
"role": "ADMIN",
"userId": 101,
"exp": 1717347600
}
Your data — identity, roles, permissions, expiry. Readable by anyone. Never put secrets here.
HMAC_SHA256( header + "." + payload, secretKey )
Cryptographic proof the token was not tampered with. Cannot be faked without the secret key.
Anyone can base64-decode the header and payload and read the contents. A signed JWT proves the data was not tampered with — it does not hide the data. Never put passwords, card numbers, or sensitive PII in a JWT.
The signature is the entire security model of JWT. HMAC_SHA256 is not encryption — it is a one-way cryptographic fingerprint. You cannot reverse it, but the same inputs always produce the same output. That determinism is the whole trick.
eyJhbGciOiJIUzI1NiJ9eyJzdWIiOiJwcmFzaGFudCJ9eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJwcmFzaGFudCJ9HMAC_SHA256(above_string, secretKey) → produces fixed-length hash SflKxwRJ...SflKxwRJ...HMAC_SHA256(header + "." + payload, secretKey) againA hacker can read the payload (it's just base64). But without the secret key, they cannot forge a valid signature. Changing "role":"user" to "role":"admin" breaks the signature — the server rejects it immediately.
HMAC_SHA256( header.payload, secretKey ) → simulated (real signing needs backend)
JWT expiry is just the exp field in the payload — a Unix timestamp. The server compares it against the current time. Pure math. No database. This statelessness is JWT's biggest architectural advantage.
| Token Type | Typical Expiry | Why |
|---|---|---|
| Access Token | 15 min – 1 hour | Short-lived, limits blast radius if stolen |
| Refresh Token | 7 – 30 days | Silently obtains a new access token without re-login |
Even though JWT is stateless by design, every serious production system adds Redis between the API Gateway and Keycloak. Here's why:
Receives every request with Bearer token
Found → forward request
Not found → 401 Reject
| Benefit | How Redis Enables It |
|---|---|
| Speed | Microsecond lookup vs. a network call to Keycloak on every single request |
| Logout | Delete key from Redis → token instantly dead, even before expiry |
| Role change | Delete key → forces re-login → fresh token with new role from DB |
In microservices, this algorithm choice is critical. HS256 shares one secret across all services. RS256 uses a public/private key pair — and that changes the entire security posture.
Keycloak exposes: https://keycloak.company.com/realms/myrealm/protocol/openid-connect/certs. Every microservice calls this once at startup, downloads the public key, and uses it forever. No secret ever leaves Keycloak. No key distribution problem.
A dedicated Auth Server — Keycloak, Auth0, Okta, or your own Spring Boot service — is the only entity that creates tokens. Before issuing one, it runs a strict verification pipeline.
Check username + password against the user database. Wrong credentials → reject 401 immediately.
SELECT role, permissions FROM users WHERE username = 'prashant' → returns role=ADMIN
Is account active? Email verified? MFA passed? IP whitelisted? In PSD2 — did the user explicitly consent to the requested scope?
The token is a snapshot of what the DB said at login time: role, permissions, scope, iat, exp.
HMAC_SHA256(header + payload, secretKey) → final token sent to the client.
Rahul cannot get ADMIN in his token because: (1) he cannot modify the payload — signature breaks; (2) he cannot forge a new signature — no secret key; (3) he cannot use Prashant's token — "sub":"prashant" is baked inside it. Three independent security guarantees.
OAuth 2.0 defines five ways to request a token — called "grant types" or "flows". Choosing the right one for your scenario is a key architectural decision.
Web apps with a backend server. User is redirected to the auth server, logs in, receives a short-lived code — not the token directly. The backend exchanges the code for the token. Token never touches the browser URL bar.
Mobile apps and SPAs. Same as above but adds a code verifier/challenge pair to prevent interception attacks. Used when client secrets cannot be safely stored on the device.
Microservice-to-microservice, no human involved. The Order Service sends its clientId + clientSecret to the auth server and receives a JWT to call the Payment Service.
User hands username + password directly to your app, which forwards them to the auth server. The app sees the password — inherently risky. Only use for legacy system migration.
Old SPAs received tokens directly in the browser URL fragment. The token was exposed in browser history and server logs. Fully replaced by Authorization Code + PKCE.
In PSD2 work, Authorization Code Flow is mandatory. The bank's customer logs in on the bank's own page and grants explicit consent before any Third Party Provider can touch their account data. That consent step is precisely what Authorization Code Flow is designed to enforce.
These three are layers, not alternatives. Each solves a distinct problem.
Just a standard way to package and sign data. Can be used completely standalone — no OAuth, no OIDC required. Your own Spring Boot auth service can issue JWTs directly without either framework.
Defines how tokens are requested and issued. Answers: "Can this app access this resource on behalf of this user?" Built for third-party access delegation. Uses JWT as its token format.
Built on top of OAuth 2.0. Adds a standardized way to ask "who is this user?" via an ID Token and a /userinfo endpoint. Enables SSO and "Login with Google" without custom integrations per provider.
When a user clicks "Login with Google", your app has no record of them. OIDC provides their identity — and your app handles registration and login in a single seamless flow.
Login happens on Google's own page. Your app never sees the password.
/userinfoGoogle returns: { "sub": "g-101", "name": "Prashant Sharma", "email": "prashant@gmail.com" }
New user? Create their record in your DB from the OIDC data. Existing user? Log them straight in. Zero forms, zero email verification, zero password creation.
Now your JWT enters the microservices world — carrying role, userId, and scope for all downstream services.
The sub claim is Google's permanent unique identifier for that user. Names change, emails change — sub never does. Always map your DB user to googleSub, not email. This is the stable foreign key to the identity provider.
| JWT Alone | OAuth 2.0 | OpenID Connect | |
|---|---|---|---|
| Third-party access | ✗ | ✓ | ✓ |
| Know who the user is | Custom only | Not standardized | ✓ Standardized |
| SSO across apps | ✗ | ✗ | ✓ |
| Login with Google/GitHub | ✗ | Not standard | ✓ |
Here is how all the pieces work together in a real microservices system — the kind of answer that separates a Senior Engineer from a Solution Architect in an interview.
key=jwt:userId:101, TTL=3600sAuthorization: Bearer <token>role and scope from payload → grants or denies access. Zero DB hit.Keycloak = OIDC + OAuth 2.0 + JWT in one place. Configure once — it handles login, Google SSO via OIDC, JWT creation with RS256, token refresh, Redis caching, scope management, and JWKS public key distribution. Every microservice verifies tokens independently, stateless, with zero DB hits per request. Infinitely scalable. This is the industry-standard pattern for enterprise microservices auth — including PSD2 Open Banking.