Try the JWT Decoder

JWT "Stateless" Is a Half-Truth — The Microservices Patterns That Account for the Other Half

JWTs are "stateless" — but deciding whether to trust the claims may require state, and confusing the two leads to architectural mistakes. Here's the microservices JWT validation dilemma (every service validates vs gateway-validates-services-trust), why encoding authorization roles in JWTs creates a stale-claims window problem, why the aud claim validation is commonly skipped (and why that's a vulnerability), and the mTLS + JWT separation of concerns that modern service meshes use.

June 23, 2026 6 min read
Share: Facebook WhatsApp LinkedIn Email
JWT "Stateless" Is a Half-Truth — The Microservices Patterns That Account for the Other Half

JWTs are commonly described as "stateless" — but this description glosses over an important caveat: the claims inside the token are stateless, but the decision of whether to trust those claims may require state, and confusing these two things leads to security decisions that seem logical but aren't

The previous articles on this site covered JWT inspection, debugging authentication errors, JWT security vulnerabilities, refresh rotation strategies, and JWT vs opaque tokens. This article addresses JWT in microservices architecture — specifically how JWTs propagate between internal services, the "gateway validates, services trust" pattern, and what can go wrong when this pattern is applied too broadly.


The microservices JWT problem: who validates the token?

In a monolithic application, JWT validation is straightforward: every request hits one application, which validates the JWT and processes the request.

In a microservices architecture, a single user request might flow through an API gateway, then trigger calls to multiple internal services (user service, order service, inventory service, payment service). Who validates the JWT?

Option 1: Every service validates the JWT — each internal service independently verifies the JWT signature and checks expiry. This is robust (each service makes its own trust decision) but has costs:

  • Every service needs access to the signing key (or JWT verification key for asymmetric algorithms)
  • JWT validation adds latency to every internal service call
  • Key rotation requires updating every service simultaneously

Option 2: Gateway validates, internal services trust — the API gateway validates the JWT and, on successful validation, forwards a trusted "identity header" to internal services. Internal services trust whatever identity they receive from the gateway without re-validating the JWT.

  • Internal services are simpler (no JWT libraries needed)
  • Key management is centralized at the gateway
  • But: if any service can be reached without going through the gateway (e.g., internal services reachable within the cluster), or if the gateway is compromised, the entire trust model fails

The service mesh approach: mTLS + JWT separation of concerns

Modern microservices infrastructure increasingly uses service meshes (Istio, Linkerd) that handle service-to-service authentication separately from user authentication:

mTLS (mutual TLS): service-to-service calls authenticate both endpoints with client certificates. Service B can verify that "this call actually came from Service A" cryptographically, without headers that could be forged.

JWTs for user identity: the user's identity (claims from the original JWT) propagate through the service call chain as a header or context object — but the authentication of the call itself (did this really come from Service A?) is handled by mTLS.

This separation of concerns is cleaner than either "every service validates the full JWT" or "services trust gateway headers": mTLS handles infrastructure-level service authentication; the JWT (or claims extracted from it) handles user-level authorization.


JWT claims for authorization vs. authentication

A common architectural mistake: putting authorization logic in JWT claims that can't be updated until token expiry.

Example: a user's permissions are encoded in the JWT: "roles": ["user", "editor"]. The user's role changes (promoted to admin). But the user still has a valid JWT token with the old "roles" claim — they don't get the "admin" role until their token expires and they get a new one.

The window between role change and token expiry is a period where the system's authorization model is wrong: the source of truth (the database) says "admin," but the JWT says "editor." Conversely, if a role is revoked (an editor loses access), they retain that access until token expiry.

Solutions:

  • Short-lived tokens: 5-15 minute access tokens mean the window is short. Users might need to re-authenticate (or use a refresh token) to pick up permission changes — acceptable if the refresh flow is seamless.
  • Check critical permissions in the database: for high-stakes authorization decisions (admin operations, payment processing), verify the current permission from the database even if the JWT claims say it's allowed. The JWT claim is a hint; the database is the truth.
  • Invalidation mechanisms: a permissions-change event can trigger token invalidation via the previously-discussed denylist approach.

JWT in machine-to-machine contexts: service tokens

JWTs aren't only for user sessions. Machine-to-machine (M2M) authentication — service accounts, API clients, automated systems — uses JWTs with different claim sets:

Standard M2M JWT claims:

  • iss (issuer): the authorization server that issued the token
  • sub (subject): the client/application identifier (not a user)
  • aud (audience): which services/APIs this token is valid for
  • exp (expiry): when the token expires
  • scope: what actions the token is authorized to perform

The aud claim is particularly important in M2M contexts: a token issued for Service A should only be accepted by Service A, not by Service B. Without aud validation, a token stolen from one service's API call can be replayed against any other service that trusts the same issuer.

Many JWT validation implementations skip aud validation — this is a security weakness. Every service that accepts JWTs should validate that the aud claim includes that service's identifier.


JWT debugging in microservices: distributed tracing integration

When a request fails authentication across a microservices system, tracing the JWT through the call chain is necessary. Practical approaches:

Correlation IDs: include a jti (JWT ID) claim and propagate it as a trace header through all internal service calls. When investigating a failed request, you can find all log entries associated with that specific JWT.

Structured logging: log JWT validation events (validation success, failure reason, token claims) in a consistent, structured format (JSON logs) at each service, enabling aggregation across services.

Not logging the JWT itself: never log the full JWT token — it's a secret equivalent to a session cookie. Log the non-sensitive claims (sub, exp, jti, aud) but not the token string.


How to use the JWT Decoder on sadiqbd.com

  1. For debugging microservice auth failures: decode the JWT at the point of failure to inspect which claims the service received — compare against expected claims (correct audience, correct scope, not expired)
  2. For M2M token inspection: verify aud and scope claims are correct for the target service; missing or wrong audience is a common M2M configuration error
  3. For permission timing issues: check the iat (issued at) and exp times — if a permission change happened between iat and exp, the JWT may carry outdated claims; the time window tells you whether this is likely

Frequently Asked Questions

Should I encode all user permissions in the JWT, or just the user ID and look up permissions per request? For most applications, a hybrid approach works best: encode coarse-grained, rarely-changing information (user ID, organization ID, basic role category) in the JWT — enough for most authorization decisions without a database call. For fine-grained, frequently-changing permissions, look up from the database when needed. This balances the "stateless JWT" efficiency benefit against the "stale claims" risk. A "is this user authenticated?" check can use the JWT alone; "is this user allowed to delete this specific resource?" may warrant a database check, especially for high-stakes operations.

Is the JWT Decoder free? Yes — completely free, no sign-up required.

Try the JWT Decoder free at sadiqbd.com — decode and inspect any JWT's header, payload, and claims instantly.

Share: Facebook WhatsApp LinkedIn Email

JWT Decoder

Free, instant results — no sign-up required.

Open JWT Decoder →
Similar Tools
Bcrypt Generator REST API Checker Timestamp Converter Password Generator UUID Generator Cron Explainer Base64 Encoder/Decoder Color Converter
JWT Decoder — Debug Authentication Errors by Inspecting Token Claims
Developer
JWT Decoder — Debug Authentication Errors by Inspecting Token Claims
JWT Security Vulnerabilities: alg:none, Algorithm Confusion, and Secure Token Storage
Developer
JWT Security Vulnerabilities: alg:none, Algorithm Confusion, and Secure Token Storage
JWT Token Refresh Strategies: Access Tokens, Refresh Rotation, and When Sessions Beat JWTs
Developer
JWT Token Refresh Strategies: Access Tokens, Refresh Rotation, and When Sessions Beat JWTs
JWT vs Opaque Session Tokens: Why This Tool Can Decode Some Tokens and Not Others
Developer
JWT vs Opaque Session Tokens: Why This Tool Can Decode Some Tokens and Not Others
Most JWT Libraries Don't Check the Audience Claim — What Gets Verified, What Doesn't, and How to Handle Revocation
Developer
Most JWT Libraries Don't Check the Audience Claim — What Gets Verified, What Doesn't, and How to Handle Revocation