Back to API Gateway
Q12Question 12 of 16

How do you propagate security tokens from an API Gateway to downstream services

Key Concepts
  • Token propagation flows the caller's authenticated identity from the gateway to downstream services.
  • Forward the Authorization header: each service re-validates the same JWT independently (purest zero trust).
  • Inject claims as headers: gateway validates once, extracts claims, forwards as X-User-Id and X-User-Roles (efficient but dangerous without mTLS).
  • Token exchange: gateway swaps the user token for a narrower, service-scoped token (least privilege).
  • Mutual TLS proves the calling service is a legitimate peer, regardless of user-level tokens.
  • These techniques combine: mTLS for service trust, token forwarding or exchange for user identity.
  • Never let downstream services accept injected claim headers from outside the gateway.

Definition

Propagate identity from the gateway downstream by forwarding the bearer token for re-validation, or injecting verified claims as trusted headers, or exchanging the user token for a narrowly-scoped internal token, all over mutual TLS.
  • Forward Authorization header – Pass the same JWT unchanged so each service re-validates independently; purest zero trust.
  • Inject claims as headers – Gateway validates once, extracts user ID and roles, forwards as X-User-Id and X-User-Roles; efficient but requires mTLS.
  • Token exchange – Gateway swaps the user token for a narrower token scoped to the specific downstream service; least privilege.
  • Mutual TLS – Proves the calling service itself is a trusted peer, separate from user-level identity.
  • Combination – Use mTLS for service-to-service trust plus token forwarding or exchange for user identity.

Imagine a customer shows ID at National Bank of India's front desk (gateway validates token). For the request to reach the loans department, the bank can send the customer's actual ID along for re-checking, write a verified summary slip stapled to the request, or issue a special internal pass that only works for the loans desk. On top of that, the internal mail is carried by verified couriers wearing bank badges (mTLS proving the caller is a real internal service).

Understanding the Concepts

What is the problem token propagation solves?

Authentication usually happens once at the edge, but downstream services must still know who the user is and whether to trust the request. Under zero trust, being inside the network is not enough.

JAVA
package com.bank.gateway;

public class PropagationProblem {
    public static void main(String[] args) {
        System.out.println("The problem:");
        System.out.println("  Gateway authenticates Rajesh at the edge");
        System.out.println("  Transfer service needs to know: WHO is transferring?");
        System.out.println("  Statement service needs to know: WHO is viewing?");
        System.out.println();
        System.out.println("Under zero trust:");
        System.out.println("  Being inside the network != being trusted");
        System.out.println("  Identity must be carried in a VERIFIABLE way");
    }
}
TEXT
Expected output:
The problem:
  Gateway authenticates Rajesh at the edge
  Transfer service needs to know: WHO is transferring?
  Statement service needs to know: WHO is viewing?

Under zero trust:
  Being inside the network != being trusted
  Identity must be carried in a VERIFIABLE way

How does forwarding the Authorization header work?

The gateway validates the JWT, then passes it unchanged to downstream services. Each service re-validates independently.

YAML
spring:
  cloud:
    gateway:
      routes:
        - id: transfer-route
          uri: lb://transfer-service
          predicates:
            - Path=/api/v1/transfers/**
          # Authorization header is forwarded by default
          # Each downstream service validates the JWT again
JAVA
package com.bank.gateway;

public class ForwardHeader {
    public static void main(String[] args) {
        System.out.println("Forward Authorization header:");
        System.out.println("  Gateway receives: Authorization: Bearer <token>");
        System.out.println("  Gateway validates token -> OK");
        System.out.println("  Gateway forwards same header to transfer-service");
        System.out.println("  Transfer-service validates token AGAIN independently");
        System.out.println();
        System.out.println("Pros: Purest zero trust, every service verifies");
        System.out.println("Cons: Full user token travels through the chain");
    }
}
TEXT
Expected output:
Forward Authorization header:
  Gateway receives: Authorization: Bearer <token>
  Gateway validates token -> OK
  Gateway forwards same header to transfer-service
  Transfer-service validates token AGAIN independently

Pros: Purest zero trust, every service verifies
Cons: Full user token travels through the chain

How does injecting claims as headers work?

The gateway validates once, extracts claims, and forwards them as plain headers. Services trust these because they trust the gateway.

JAVA
package com.bank.gateway;

import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.cloud.gateway.filter.GlobalFilter;
import org.springframework.core.Ordered;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;

@Component
public class ClaimInjectionFilter implements GlobalFilter, Ordered {

    private final JwtUtil jwtUtil;

    public ClaimInjectionFilter(JwtUtil jwtUtil) {
        this.jwtUtil = jwtUtil;
    }

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
        String token = extractToken(exchange.getRequest());
        if (token != null) {
            String userId = jwtUtil.extractUsername(token);
            String roles = jwtUtil.extractRoles(token);

            ServerHttpRequest modified = exchange.getRequest().mutate()
                    .header("X-User-Id", userId)
                    .header("X-User-Roles", roles)
                    .build();
            return chain.filter(exchange.mutate().request(modified).build());
        }
        return chain.filter(exchange);
    }

    @Override
    public int getOrder() { return -1; }

    private String extractToken(ServerHttpRequest request) {
        String auth = request.getHeaders().getFirst("Authorization");
        return (auth != null && auth.startsWith("Bearer ")) ? auth.substring(7) : null;
    }
}
TEXT
Claim injection:
  Gateway adds: X-User-Id: rajesh, X-User-Roles: CUSTOMER
  Downstream reads headers directly (no JWT parsing)

  DANGER: Only safe if gateway is the SOLE entry point
  Services must reject X-User-Id headers from external sources
  Combine with mTLS so only the gateway can set these headers

How does token exchange provide least privilege?

The gateway presents the user's token to the identity provider and gets a narrower token scoped to the specific downstream.

JAVA
package com.bank.gateway;

public class TokenExchange {
    public static void main(String[] args) {
        System.out.println("Token exchange flow:");
        System.out.println("  1. Gateway has Rajesh's broad token (aud: gateway)");
        System.out.println("  2. Gateway calls IdP: exchange for transfer-service token");
        System.out.println("  3. IdP returns narrow token (aud: transfer-service, scope: transfer)");
        System.out.println("  4. Gateway forwards narrow token to transfer-service");
        System.out.println();
        System.out.println("Benefit: least privilege");
        System.out.println("  Transfer-service token cannot be reused at statement-service");
        System.out.println("  A compromised transfer-service cannot call other services");
        System.out.println();
        System.out.println("Cost: extra call to IdP per request (cacheable)");
    }
}
TEXT
Expected output:
Token exchange flow:
  1. Gateway has Rajesh's broad token (aud: gateway)
  2. Gateway calls IdP: exchange for transfer-service token
  3. IdP returns narrow token (aud: transfer-service, scope: transfer)
  4. Gateway forwards narrow token to transfer-service

Benefit: least privilege
  Transfer-service token cannot be reused at statement-service
  A compromised transfer-service cannot call other services

How does mTLS prove service identity?

Mutual TLS establishes that the calling service is a legitimate peer, separate from user-level tokens.

JAVA
package com.bank.gateway;

public class MtlsServiceIdentity {
    public static void main(String[] args) {
        System.out.println("mTLS proves SERVICE identity:");
        System.out.println("  Gateway has certificate: CN=api-gateway");
        System.out.println("  Transfer-service has certificate: CN=transfer-service");
        System.out.println("  Both present and verify each other's certificates");
        System.out.println();
        System.out.println("  Attacker without valid certificate:");
        System.out.println("    Cannot call transfer-service even with a stolen user token");
        System.out.println("    TLS handshake fails before any HTTP traffic flows");
    }
}
TEXT
Expected output:
mTLS proves SERVICE identity:
  Gateway has certificate: CN=api-gateway
  Transfer-service has certificate: CN=transfer-service
  Both present and verify each other's certificates

  Attacker without valid certificate:
    Cannot call transfer-service even with a stolen user token
    TLS handshake fails before any HTTP traffic flows

How do these techniques compare?

| Technique | Who is identified | Trust model | Pros | Cons |
|---|---|---|---|---|
| Forward header | User | Each service verifies JWT | Purest zero trust | Full token in chain |
| Inject claims | User | Services trust gateway | Fast, no JWT parsing | Dangerous without mTLS |
| Token exchange | User | Narrow-scoped tokens | Least privilege | Extra IdP call |
| mTLS | Service | Certificate-based | Blocks rogue callers | Does not identify user |

TEXT
Combination at NBI:
  mTLS -> proves transfer-service is calling, not an attacker
  Token exchange -> Rajesh's identity in a narrow, transfer-scoped token
  Result: both SERVICE and USER identity are verified

What are the WRONG patterns?

JAVA
package com.bank.gateway;

public class PropagationAntiPatterns {
    // WRONG: Accept X-User-Id from external requests
    // An attacker sets X-User-Id: admin and bypasses auth

    // WRONG: Forward the original broad token everywhere
    // A compromised downstream holds a usable token for all services

    // CORRECT: Strip external X-User-Id headers at the gateway
    // CORRECT: Use token exchange for least privilege
    // CORRECT: Validate audience at each service

    public static void main(String[] args) {
        System.out.println("WRONG: Accept X-User-Id from external request");
        System.out.println("  Attacker: X-User-Id: admin -> instant privilege escalation");
        System.out.println();
        System.out.println("CORRECT: Gateway strips external X-User-Id headers");
        System.out.println("  Only gateway-injected headers are trusted");
        System.out.println("  Enforced via mTLS: only gateway has valid certificate");
    }
}
TEXT
Expected output:
WRONG: Accept X-User-Id from external request
  Attacker: X-User-Id: admin -> instant privilege escalation

CORRECT: Gateway strips external X-User-Id headers
  Only gateway-injected headers are trusted
  Enforced via mTLS: only gateway has valid certificate

What should a senior engineer know?

Never let downstream services accept injected headers from outside the gateway. Prefer token exchange for per-service audience restriction. Keep forwarded tokens short-lived. Validate issuer and audience at every hop.

TEXT
Senior guidelines:
  [x] mTLS for service-to-service trust
  [x] Token exchange for least-privilege audience scoping
  [x] Strip external claim headers at the gateway
  [x] Validate aud at each downstream service
  [x] Propagate correlation/trace ID alongside the token
  [x] Keep forwarded tokens short-lived
  [x] Log authorization decisions at each hop for audit

Visual Overview

DIAGRAM
  Token Propagation Options:

  Option 1: Forward Header
  ┌────────┐  Bearer <token>  ┌─────────┐  Bearer <token>  ┌──────────┐
  │ Client │────────────────▶│ Gateway │────────────────▶│ Service  │
  └────────┘                 │validates│                 │validates │
                             └─────────┘                 │  again   │
                                                         └──────────┘

  Option 2: Inject Claims
  ┌────────┐  Bearer <token>  ┌─────────┐  X-User-Id: rajesh  ┌──────────┐
  │ Client │────────────────▶│ Gateway │────────────────────▶│ Service  │
  └────────┘                 │validates│  X-User-Roles: CUST  │ trusts   │
                             │extracts │                      │ gateway  │
                             └─────────┘                      └──────────┘

  Option 3: Token Exchange
  ┌────────┐  Bearer <user>   ┌─────────┐  exchange  ┌─────┐
  │ Client │────────────────▶│ Gateway │───────────▶│ IdP │
  └────────┘                 └────┬────┘            └──┬──┘
                                  │  Bearer <narrow>    │
                                  │◀────────────────────┘
                                  ▼
                             ┌──────────┐
                             │ Service  │ validates narrow token
                             │ (aud:    │ scoped to this service
                             │  this-svc│
                             └──────────┘

Key Takeaway

Token propagation flows the caller's authenticated identity from the gateway to downstream services. The simplest approach forwards the same Authorization header so each service re-validates the JWT independently, which is the purest zero-trust form. Injecting claims as headers is more efficient but dangerous without mTLS because any component that can set those headers can impersonate a user. Token exchange is the most robust pattern: the gateway swaps the user token for a narrower one scoped to the specific downstream, giving least privilege.

Mutual TLS addresses a different question: whether the calling service itself is a legitimate peer. These techniques combine: mTLS for service-to-service trust, plus token forwarding or exchange for user identity. At National Bank of India, Pankaj used mTLS through the mesh for service trust and token exchange for the transfer service so it received a token scoped only to itself, satisfying the security team's isolation requirement.

The critical anti-pattern is accepting injected claim headers from external sources. An attacker setting X-User-Id: admin on an external request would get instant privilege escalation. The gateway must strip such headers from external requests and only inject them after validation, enforced by mTLS so only the gateway's certificate is accepted.

Remember one sentence: propagate identity from the gateway by forwarding the bearer token, injecting verified claims behind mTLS, or exchanging for a narrowly-scoped token, so downstream services know both who the user is and that the request came from a trusted peer.

---

Share
Use ← → arrow keys to navigate