Home/Learn/Microservices/JWT in Microservices

JWT in Microservices

Intermediate
Security

The API gateway validates the JWT and forwards claims downstream as trusted headers; each service reads claims without needing a round-trip to the auth server.

Overview

JSON Web Tokens (JWT) are the dominant authentication mechanism in microservice architectures because they are stateless: the token contains signed claims (userId, roles, tenantId) that any service can verify using the authorisation server's public key without a database roundtrip. The typical flow: the client authenticates against an auth server (Keycloak, Auth0, Okta) and receives an access token (JWT). The client sends the JWT as a Bearer token; the API gateway validates it (signature + expiry + issuer) and forwards the decoded claims as trusted headers (X-User-Id, X-Roles) to downstream services. Downstream services trust the gateway and read claims from headers, avoiding redundant token validation overhead.

JWT structure and validation

A JWT has three Base64URL-encoded parts separated by dots: header (algorithm), payload (claims), and signature. Claims include standard fields (iss, sub, exp, iat) and custom application claims (roles, tenantId). Validation checks: signature (using JWKS public key), expiry (exp), issuer (iss), audience (aud). Spring Security Resource Server does all this automatically with the jwks-uri config.

YAML — JWT structure and Spring Boot Resource Server auto-validation
# JWT structure (decoded)
# Header:  {"alg": "RS256", "typ": "JWT", "kid": "key-id-1"}
# Payload: {
#   "sub": "user-123",         ← subject (user id)
#   "iss": "https://auth.example.com",
#   "aud": "order-service",
#   "exp": 1716840000,         ← expiry unix timestamp
#   "iat": 1716836400,
#   "roles": ["USER", "ORDER_MANAGER"],
#   "tenantId": "tenant-abc"
# }
# Signature: RS256(base64(header) + "." + base64(payload), private_key)

# application.yml — resource server validates JWT automatically
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://auth.example.com        # fetches JWKS from /.well-known/openid-configuration
          # or explicitly:
          jwks-uri: https://auth.example.com/.well-known/jwks.json

# Spring Security auto-validates: signature, exp, iss, aud
# Access current user in controller:
# @AuthenticationPrincipal Jwt jwt
# jwt.getClaimAsString("tenantId")
# jwt.getClaimAsStringList("roles")

API gateway token validation and claims forwarding

The API gateway (Spring Cloud Gateway, Nginx, Kong) validates the JWT once and forwards decoded claims as HTTP headers to downstream services. Downstream services trust these headers without re-validating the JWT, reducing latency and auth-server load. Ensure downstream services only accept these headers from trusted network sources (the gateway), never directly from clients.

Java — API gateway JWT validation and claims header forwarding
// Spring Cloud Gateway — validate JWT and forward claims as headers
@Component
public class JwtClaimsForwardingFilter implements GlobalFilter {

    private final JwtDecoder jwtDecoder;

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            return chain.filter(exchange);
        }
        String token = authHeader.substring(7);
        try {
            Jwt jwt = jwtDecoder.decode(token);
            ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
                .header("X-User-Id", jwt.getSubject())
                .header("X-Roles", String.join(",", jwt.getClaimAsStringList("roles")))
                .header("X-Tenant-Id", jwt.getClaimAsString("tenantId"))
                .build();
            return chain.filter(exchange.mutate().request(mutatedRequest).build());
        } catch (JwtException e) {
            exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
            return exchange.getResponse().setComplete();
        }
    }
}

// Downstream service reads trusted headers (no JWT validation needed)
@GetMapping("/orders")
public List<Order> getOrders(
        @RequestHeader("X-User-Id") String userId,
        @RequestHeader("X-Tenant-Id") String tenantId) {
    return orderRepository.findByUserAndTenant(userId, tenantId);
}

Token propagation and service-to-service calls

When a service makes downstream calls on behalf of the user, it must propagate the original JWT (token relay) so the downstream service can enforce authorisation. Spring Security's TokenRelayGatewayFilterFactory and Feign's request interceptor automate this. For machine-to-machine calls where no user context is present, use the Client Credentials flow to obtain a service-specific token.

Java — JWT token relay via Feign interceptor and Spring Cloud Gateway
// Feign client — propagate incoming JWT to downstream calls
@Component
public class JwtPropagatingInterceptor implements RequestInterceptor {

    @Override
    public void apply(RequestTemplate template) {
        // Extract JWT from Spring Security context
        Authentication auth = SecurityContextHolder.getContext().getAuthentication();
        if (auth instanceof JwtAuthenticationToken jwtAuth) {
            template.header("Authorization",
                "Bearer " + jwtAuth.getToken().getTokenValue());
        }
    }
}

// Spring Cloud Gateway — token relay filter (auto-propagates access token)
# application.yml (gateway)
spring:
  cloud:
    gateway:
      routes:
        - id: order-service
          uri: lb://order-service
          predicates:
            - Path=/api/orders/**
          filters:
            - TokenRelay=     # automatically forwards the access token

// Access current JWT claims anywhere in the service
@Service
public class OrderService {
    public Order placeOrder(OrderRequest req,
                            @AuthenticationPrincipal Jwt jwt) {
        String userId = jwt.getSubject();
        String tenantId = jwt.getClaimAsString("tenantId");
        // ...
    }
}

Key Points to Remember

  • 1JWTs are stateless signed tokens — any service with the public key can validate them without calling the auth server
  • 2API gateway validates JWT once and forwards decoded claims as trusted headers (X-User-Id, X-Roles) downstream
  • 3Downstream services must only trust claim headers from internal gateway network, never from external clients
  • 4Spring Security Resource Server auto-validates JWT signature, expiry, issuer, and audience with zero custom code
  • 5Token relay propagates the original JWT on downstream calls; use Client Credentials for machine-to-machine calls
  • 6JWT revocation challenge: tokens are valid until expiry — use short expiry (5–15 min) + refresh tokens or a revocation list

Interview Questions

Sign in to ask Aria
1

What are the three parts of a JWT and what does each contain?

EasyInfosys
2

How does an API gateway improve performance when validating JWTs in a microservice architecture?

MediumAmazon
3

How would you implement JWT revocation for short-lived access tokens?

HardOkta
4

What is the token relay pattern and when would you use it?

MediumThoughtworks
5

How do you propagate tenant context (tenantId) from a JWT through a microservice call chain?

HardNetflix

Ask Aria about JWT in Microservices

Your personal AI tutor — ask anything about this concept

Revision Status

Personal Notes

Sign in to save personal notes for this topic.

Discussion

Sign in to join the discussion.

Loading discussion…