Skip to content

JWT Internals#

Problem statement (interviewer prompt)

A microservice fleet must authenticate millions of requests/sec without hitting a session store. Design a token format that the issuer can sign, every service can verify locally, and the client can carry across requests. What are the failure modes?

A JWT is three base64url segments separated by dots: header.payload.signature. Each service holds the issuer's public key and verifies the signature locally; no database lookup per request.

JSON Web Token (JWT) logo
JWT logo, via Wikimedia Commons. CC BY-SA.
flowchart LR
  C([Client]) --> Auth[Auth service<br/>issues JWT]
  Auth -->|signed JWT| C
  C --> Svc1[Service A]
  C --> Svc2[Service B]
  Svc1 -. verify with issuer's public key .-> JWKS[(JWKS endpoint)]
  Svc2 -. verify .-> JWKS

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class C client;
    class Auth,Svc1,Svc2 service;
    class JWKS datastore;

Sounds wonderful; comes with pitfalls. Most JWT bugs are about how revocation, expiry, and signing-key rotation work.

JWT (RFC 7519) is the most common stateless authentication token. It's simple by design and routinely misused; understanding the moving parts is what separates safe deployments from disasters.

Anatomy#

eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCIsImtpZCI6IjEyMyJ9
.
eyJzdWIiOiJ1c2VyXzQyIiwidGVuYW50IjoiYWNtZSIsImlhdCI6MTcwMDAwMDAwMCwiZXhwIjoxNzAwMDAzNjAwfQ
.
<signature>

Three base64url-encoded JSON segments:

Segment Example
Header {"alg":"RS256","typ":"JWT","kid":"123"}
Payload {"sub":"user_42","tenant":"acme","iat":1700000000,"exp":1700003600}
Signature RSA-SHA256(secret_or_private_key, header + "." + payload)

Signing algorithms#

alg Type Use
HS256 Symmetric (HMAC) Single trusted party; secret shared between issuer and verifier
RS256 RSA signature Federation; verifiers hold public key only
ES256 ECDSA signature Same as RS256 but smaller keys
EdDSA Ed25519 signature Modern; recommended for new systems
none UNSIGNED NEVER accept; classic attack

JWKS - rotating keys#

sequenceDiagram
  participant C as Client
  participant Auth as Auth service
  participant Svc as API service
  participant JWKS as /.well-known/jwks.json
  Auth->>JWKS: publish new public key (kid=v2)
  C->>Auth: login -> JWT signed with v2
  C->>Svc: request + JWT(kid=v2)
  Svc->>JWKS: GET keys (cached)
  Svc->>Svc: verify with kid=v2
  Note over Auth: rotate, old kid=v1 still valid for old tokens

Best practice: issuer exposes a JWKS endpoint with current and prior public keys. Verifiers fetch and cache them, indexed by kid. Rotate keys regularly; never blast all old tokens by removing keys before they expire.

Common attacks#

1. alg: none#

Old JWT libraries accepted alg: none (no signature). Attacker forges any payload. Fix: enforce alg allowlist server-side.

2. Algorithm confusion#

If your server holds an RS256 public key and the library accepts both RS256 and HS256 with the same secret, an attacker can sign HS256 using the public key as the HMAC secret. Fix: bind alg per key.

3. kid injection#

kid field used to look up a key in the filesystem or DB without validation: attacker uses ../../etc/passwd or a SQL-injected kid. Fix: validate kid against an allowlist.

4. Stolen token replay#

JWTs are bearer tokens. If stolen (XSS, log leak, network sniffing), the attacker uses them until expiry. Fixes:

  • Short expiry (5-15 min).
  • Refresh tokens stored differently and rotatable.
  • Bind to a fingerprint (TLS exporter, DPoP).

Revocation - the hard part#

JWTs are stateless; there's no per-token "logout" without state. Options:

Strategy Cost
Wait for expiry Simplest; needs short exp
Blocklist in Redis Stateful again; verify per request
Token versioning Embed user's "token generation"; bump on logout
Refresh-token rotation Short JWT + long refresh; revoke refresh server-side

Most production systems use refresh-token rotation with short access JWTs.

Refresh-token flow#

sequenceDiagram
  participant C as Client
  participant A as Auth
  participant R as Refresh store
  C->>A: login
  A->>C: access JWT (15m) + refresh token (7d)
  Note over C: store refresh in httpOnly cookie
  C->>A: refresh (when access about to expire)
  A->>R: validate, rotate, revoke old
  A->>C: new access + new refresh

Refresh tokens are opaque random strings stored server-side; they support revocation directly.

Standard claims (RFC 7519)#

Claim Meaning
iss Issuer
sub Subject (user id)
aud Audience (intended recipient)
exp Expiration time (unix seconds)
nbf Not-before time
iat Issued-at
jti Unique token id

Always verify exp, iss, aud, and clock skew (leeway: 30s).

Token size#

A JWT is large compared to opaque tokens:

  • Typical: 700 bytes - 2 KB.
  • Every request carries it - bandwidth tax on chatty APIs.
  • Cookies have a 4 KB cookie + 8 KB header limit; chunky JWTs break things.

When NOT to use JWT#

  • Highly sensitive sessions where instant revocation matters.
  • Endpoints behind a single trust boundary (just use sessions).
  • High-frequency RPC where overhead matters.

Where JWTs are used#

flowchart TB
  JWT((JWT))
  CRY[Crypto primitives<br/>signing math]
  SEC[Security fundamentals<br/>AuthN/AuthZ context]
  OAUTH[OAuth / SSO<br/>issuance layer]
  GW[API Gateway<br/>typical validator]
  CRY --> JWT
  SEC --> JWT
  OAUTH --> JWT
  JWT --> GW

    classDef client fill:#dbeafe,stroke:#1e40af,stroke-width:1px,color:#0f172a;
    classDef edge fill:#cffafe,stroke:#0e7490,stroke-width:1px,color:#0f172a;
    classDef service fill:#fef3c7,stroke:#92400e,stroke-width:1px,color:#0f172a;
    classDef datastore fill:#fee2e2,stroke:#991b1b,stroke-width:1px,color:#0f172a;
    classDef cache fill:#fed7aa,stroke:#9a3412,stroke-width:1px,color:#0f172a;
    classDef queue fill:#ede9fe,stroke:#5b21b6,stroke-width:1px,color:#0f172a;
    classDef compute fill:#d1fae5,stroke:#065f46,stroke-width:1px,color:#0f172a;
    classDef storage fill:#e5e7eb,stroke:#374151,stroke-width:1px,color:#0f172a;
    classDef external fill:#fce7f3,stroke:#9d174d,stroke-width:1px,color:#0f172a;
    classDef obs fill:#f3e8ff,stroke:#6b21a8,stroke-width:1px,color:#0f172a;
    class JWT service;
    class CRY,SEC,OAUTH,GW datastore;

Glossary & fundamentals#

Tag Concept What it is Page
HLD Cryptography primitives the signing layer cryptography-primitives
HLD Security fundamentals parent topic security-fundamentals
HLD OAuth SSO issues JWTs oauth-sso
HLD API gateway typical JWT validator api-gateway

Quick reference#

Structure#

base64url(header).base64url(payload).base64url(signature)

Algorithms#

alg Notes
HS256 Single trusted party (shared secret)
RS256 Federation; verifiers hold public key
ES256 RS256 alternative, smaller keys
EdDSA Modern; recommended
none NEVER accept

Standard claims#

iss, sub, aud, exp, nbf, iat, jti.

JWKS#

  • /.well-known/jwks.json publishes current + prior keys
  • Verifier caches, indexes by kid
  • Rotate regularly; keep old keys until tokens expire

Attacks#

  • alg: none - enforce allowlist
  • Algorithm confusion - bind alg per key
  • kid injection - validate against allowlist
  • Token replay - short exp, refresh rotation, fingerprint binding

Revocation strategies#

Strategy Cost
Wait for exp Simplest
Blocklist in Redis Stateful again
Token version per user Bump on logout
Refresh rotation Common production pattern

Refresh flow#

  • Access JWT: 5-15 min
  • Refresh token: opaque, 7-30 day, server-side, rotatable
  • httpOnly secure cookie

Verify checklist#

  • alg in allowlist
  • Signature
  • exp with clock skew leeway
  • iss, aud
  • nbf if present

Size#

700 bytes - 2 KB typical; tax on every request.

Refs#

  • RFC 7519
  • RFC 8725 (JWT BCP)
  • OWASP JWT Cheat Sheet
  • Auth0 JWT handbook

FAQ#

What is a JWT?#

A JSON Web Token is three base64url segments separated by dots: header, payload, and signature. The issuer signs it so any service can verify without a database lookup.

Use JWT for stateless microservice auth where every service must verify cheaply. Use sessions when you need instant revocation and tighter control over each login.

How do you revoke a JWT?#

Keep tokens short-lived and pair them with a refresh token. For instant revocation, check a denylist or a versioned subject claim on sensitive operations.

What is JWKS?#

A JWKS endpoint publishes the issuer's signing public keys. Services cache the keys, verify the kid header on each token, and rotate without downtime.

Are JWT payloads encrypted?#

Signed JWTs are not encrypted, only tamper-evident, so the payload is readable. Use JWE if you actually need to keep claims secret from the holder.

Further reading#

Video walkthrough

Why is JWT Popular? : via ByteByteGo