Teams that validate OAuth access tokens only at the API gateway often discover that their services will accept any request that reaches them by another route. The opposite choice, leaving every service to parse and check tokens on its own, tends to produce dozens of slightly different validators with different audience rules, clock skew and key caching. This guide compares the two placements as an architecture decision: what each layer can and cannot prove, the threat model for each, how they fail, and how to combine them. It is written for API platform owners, security architects and service teams running microservices behind a gateway or service mesh. Gateway configuration detail lives in the API Gateway Security Guide and the JWT checks themselves in the JWT Security Guide; this page is about where those checks belong.
Key takeaways
- The short answer is both, with different jobs. The gateway rejects bad tokens early and enforces coarse route access; each service validates the token again (or a token minted for it) and makes the object, tenant and business decision that only it can make.
- A gateway check proves nothing to a service that can be reached another way. The OWASP Microservices Security Cheat Sheet lists direct access that bypasses the gateway and services that trust the gateway as the core risks of gateway-only authorisation.
- Audience is the check most often missing. RFC 9068 makes
audrequired for JWT access tokens and says the resource server must reject a token whose audience does not include it; a gateway that checksaudfor itself does not stop internal replay between services. - Forwarding the user's original token deep into the call chain widens its blast radius. Token exchange (RFC 8693) or a short-lived internal token scoped to the next hop limits what a captured token can do.
- Choose local or introspection validation by revocation need, not habit. Local JWT checks are fast but miss revocation until expiry; RFC 7662 introspection is fresh but adds latency and a dependency.
What each layer can actually prove
The two placements answer different questions. Treating them as alternatives is where most designs go wrong.
| Question | API gateway (edge) | In-service validation |
|---|---|---|
| Is this token genuine, unexpired and from our issuer? | Yes, cheaply, before any backend cost | Yes, if the service validates fully rather than trusting a header |
| Was this token issued for this specific API? | Only for the audience the gateway is configured with | Yes, against the service's own audience identifier |
| May this caller use this route or method? | Yes, from scopes or roles mapped to routes | Yes, and it can apply function rules the gateway does not know |
| May this caller read this object, tenant or record? | No: the gateway does not hold the data or ownership model | Yes: object and tenant checks need the service's data |
| Did this request come through the gateway at all? | Not applicable | Only if the service verifies something the gateway alone can produce, such as a signed token or mTLS peer identity |
| Which upstream service is calling on the user's behalf? | Not visible for east-west calls | Yes, with workload identity (mTLS or a service token) alongside user context |
The OWASP Microservices Security Cheat Sheet states the limit directly: gateway checks reject unauthorised ingress but do not establish that a downstream operation is authorised. Fine-grained decisions that need resource or business context belong in the service. In OWASP API Security Top 10 2023 terms, the gateway helps with API2:2023 Broken Authentication and part of API5:2023 Broken Function Level Authorization, while API1:2023 Broken Object Level Authorization can only be fixed in the service. See the OWASP API Top 10 API1 Broken Object Level Authorization Guide.
Option A: validate at the API gateway
Architecture in words
The client presents a bearer token to the gateway. The gateway fetches the issuer's signing keys (JWKS), verifies signature, issuer, audience, expiry and token type, maps required scopes to routes, and forwards the request to the backend. It usually passes identity downstream either by forwarding the original token or by copying verified claims into headers. Envoy's jwt_authn filter, for example, has forward to pass the JWT upstream, forward_payload_header to send the decoded payload in a named header and claim_to_headers to copy individual claims; the filter strips those header names from incoming requests so clients cannot spoof them.
Threat model
- Gateway bypass: a pod, VM, function URL, load balancer or legacy route that reaches the backend without passing the gateway. If the backend trusts identity headers, any caller with network reach becomes any user.
- Header spoofing: a backend that reads
X-User-Idor similar from any source. The control is that the gateway always overwrites the header and the backend accepts it only from an authenticated gateway. - Insider and lateral movement: once an attacker controls one service, gateway checks offer no protection for east-west calls. MITRE ATT&CK T1550.001 Use Alternate Authentication Material: Application Access Token describes stolen tokens used to move laterally.
- Silent misconfiguration: Envoy's
jwt_authndocumentation notes that ifaudiencesis not specified, the audience is not checked. One missing line accepts tokens for every API under the same issuer.
Strengths
- One validator to get right: issuer pinning, algorithm allow lists, JWKS caching and clock skew are set once and reviewed once.
- Early rejection: invalid tokens never reach application code, which cuts load and narrows attack surface.
- Consistent errors and logging: 401 for missing or invalid tokens, 403 for insufficient scope, and one place to log both.
Option B: validate in each service
Architecture in words
Each service (or the sidecar beside it) receives the token, validates it against its own audience and makes its own authorisation decision. In a mesh, Istio's RequestAuthentication resource configures issuer, audiences and jwksUri per workload, and outputClaimToHeaders copies verified claims into headers for the application. Service-to-service calls carry workload identity as well, through mTLS or a service token. NIST SP 800-207A describes this model for cloud-native applications: access decisions based on application and service identities rather than network location, enforced through API gateways, sidecar proxies and identity infrastructure such as SPIFFE.
Threat model
- Validator drift: forty services, six languages and several JWT libraries produce inconsistent algorithm handling, missing
typchecks or permissive skew. RFC 8725 section 3.1 requires an explicit allow list of algorithms; each library has to be configured to honour it. - Accept-if-absent defaults: Istio's documentation states that
RequestAuthenticationrejects invalid tokens but accepts requests with no token at all, which then have no authenticated identity. Requests are only refused if anAuthorizationPolicyrequiresrequestPrincipalsor the JWT rule setspresencetoREQUIRED. - Key and revocation sprawl: every service fetches JWKS on its own schedule, and none of them sees a revocation that local validation cannot observe.
- Developer error under deadline: a service that decodes a token without verifying it, or verifies signature but not audience, passes functional tests.
Strengths
- No trust in the network path: the service is safe even when reached directly, which is the zero trust property NIST SP 800-207A asks for.
- Real authorisation: only the service can check that order 4411 belongs to the caller's tenant.
- Per-service audience: a token for the billing API is rejected by the HR API even inside the cluster.
Head-to-head comparison
| Criterion | Gateway-only | Service-only | Layered (both) |
|---|---|---|---|
| Protection against gateway bypass | None | Full | Full |
| Object and tenant authorisation | Not possible | Yes | Yes, in the service |
| Consistency of token checks | High | Low unless a shared library or sidecar is mandated | High at edge, enforced by shared library or mesh inside |
| Latency cost | One validation | One per hop | One per hop; local JWT checks are cheap once keys are cached |
| Internal replay of a captured token | Unrestricted | Limited by per-service audience | Limited by audience plus token exchange |
| Operational ownership | Platform team | Every service team | Platform owns the validator, service teams own authorisation rules |
| Fit | Single backend, private network enforced, low data sensitivity | Mesh with strong platform tooling | Most production microservice estates |
Token forwarding: the decision inside the decision
Once the gateway has validated a user's token, the next question is what travels to the backend and onward to the services it calls.
- Forward the original token: simplest, and every service can validate it. The cost is that the token's audience must cover every service it reaches, so a single captured token works everywhere. RFC 9068 requires distinct
audvalues per resource to prevent cross-JWT confusion, which forwarding works against. - Pass verified claims in headers: fast, but the service is trusting the network path. Acceptable only when the backend accepts those headers solely from an mTLS-authenticated gateway identity, and the gateway always strips client copies.
- Exchange for a downstream token: the gateway or calling service uses RFC 8693 token exchange, presenting the user's token as
subject_tokenand its own credential asactor_token, and requests a new token with the next service'saudienceorresource. In delegation mode the issued token carries anactclaim naming the actor, and only the current actor is used for access decisions. RFC 8693 warns that the token service must authenticate the requesting client, otherwise anyone holding a compromised token could mint others. - Internal signed assertion: the gateway mints a short-lived token for internal use, signed by a key services trust, carrying the user subject and the gateway as actor. This is in effect a local token exchange and needs the same discipline: short lifetime, narrow audience, explicit
typ.
See the OAuth Token Exchange vs Token Passthrough comparison for the full trade-off.
Controls for the layered model
At the gateway
- Full validation: signature with an algorithm allow list, exact
issmatch,audnaming the API,exp, andtypofat+jwtwhere your issuer supports RFC 9068. - Route to scope map: every route has a required scope or role, unknown routes return 404, and the map is kept in version control.
- Header hygiene: strip inbound identity headers, then set them from verified claims only.
- Fail closed: if validation or an external authoriser cannot complete, reject. The OWASP cheat sheet makes this a recommendation in its own right.
In each service
- Shared validator, not hand-rolled code: a platform library or sidecar with the same issuer, algorithm and skew settings as the gateway, configured with the service's own audience.
- Require a principal: in Istio, pair
RequestAuthenticationwith anAuthorizationPolicyrequiringrequestPrincipals, or setpresence: REQUIRED; in libraries, reject requests with no token rather than treating them as anonymous. - Object and tenant checks: enforce ownership on every read and write, including internal calls.
- Workload identity: authenticate the calling service separately from the user, through mTLS or a service token. See the mTLS vs Signed Tokens for Service Authentication comparison.
In the network
- Remove bypass routes: backends accept traffic only from the gateway or mesh, enforced by network policy and by verifying the gateway's mTLS identity.
- Sender-constrained tokens for high-value APIs: RFC 9700 recommends mTLS-bound tokens (RFC 8705) or DPoP (RFC 9449) so a stolen token cannot be replayed elsewhere. The resource server must perform the proof-of-possession check, so decide which layer holds that duty. See the DPoP Guide.
Local validation or introspection
| Factor | Local JWT validation | Introspection (RFC 7662) |
|---|---|---|
| Revocation visibility | None until exp | Immediate, apart from any cache window |
| Latency | Local computation only once JWKS is cached | A network call per token or per cache miss |
| Dependency | JWKS endpoint availability | Authorisation server availability on the request path |
| Token format | JWT only | JWT or opaque reference tokens |
| Security notes | Short lifetimes compensate for missing revocation | Endpoint requires caller authentication; cache must never outlive exp |
A common pattern is introspection at the gateway for opaque external tokens, followed by an internal JWT for east-west calls. RFC 7662 notes that the cache period trades freshness against load and that highly sensitive systems may disable caching. See the OAuth Token Introspection and Revocation Guide and JWT vs Opaque Tokens.
Decision guidance
- Single backend behind a private gateway: gateway validation plus a service check of a gateway-signed assertion or mTLS identity is usually proportionate. Still check object ownership in the application.
- Microservices on Kubernetes with a mesh: validate at the ingress gateway and again at each workload through the mesh, with per-service audiences and
requestPrincipalsrequired. - Multi-tenant SaaS APIs: layered validation is the minimum because tenant isolation is an object-level rule that the gateway cannot see.
- Partner and B2B APIs: sender-constrained tokens and introspection for revocation, with the gateway terminating client mTLS.
- Small organisations: start with gateway validation done well and a single shared library in services; add token exchange when the number of internal hops makes forwarding risky.
- Large organisations: mandate the validator as a platform component, forbid service teams from writing their own token parsing, and audit configuration centrally.
Worked scenario
A retailer exposes an orders API through a gateway. The gateway validates tokens with audience https://api.example.com/orders and forwards the original token to the orders service, which calls payments and loyalty services with the same token. A misconfigured internal load balancer exposes the payments service to the corporate network. Under gateway-only validation, anyone on that network can call payments with a forged identity header. Under the layered design, payments requires a token with audience payments, obtained by the orders service through token exchange with itself as actor, plus the orders service's mTLS identity. The exposed endpoint now rejects both header spoofing and a replayed customer token, and the logs show which actor requested each payment token.
How to implement
- Inventory paths: list every route by which each backend can be reached, including health and admin ports, function URLs and old load balancers. Close or authenticate each one.
- Standardise the validator: choose one library or sidecar configuration with issuer, algorithm allow list, skew and JWKS cache settings matching the gateway.
- Assign audiences: give each API a distinct audience and configure the authorisation server to issue per-resource tokens, using RFC 8707 resource indicators where supported.
- Run in audit mode: deploy in-service validation logging failures without rejecting, for one or two release cycles. Fix clients that send wrong audiences or expired tokens.
- Enforce in rings: switch to rejection for low-risk services first, then data-sensitive ones. Keep a feature flag per service so you can revert to audit mode quickly if a client breaks.
- Introduce token exchange: replace original-token forwarding on the deepest call chains first.
- Test bypass: add automated tests that call each service directly without a token, with a forged header and with a token for another audience. All must fail.
Detection and measurement
- Log at both layers: validation outcome, reason code (bad signature, wrong audience, expired, missing),
sub,client_id, actor and route. Never log the token itself; see the JWT and Session Tokens in Code and Logs Guide. - Alert on: service-level 401s for requests that never appear in gateway logs (a bypass signal), audience mismatches inside the cluster (replay or misrouting), and spikes of tokens accepted by services without a principal.
Metrics that show the model is working:
- Percentage of services enforcing in-service validation with their own audience (target 100% for data-bearing services).
- Number of reachable backend endpoints that bypass the gateway (target zero).
- Percentage of internal calls using exchanged or internal tokens rather than forwarded user tokens.
- Count of distinct token validation configurations in use (falling towards one).
- Mean time from token revocation to rejection at the resource server.
Common failure patterns
- Header trust without path control: backends read identity headers from any source, so a single exposed port becomes an authentication bypass.
- Empty audience settings: Envoy skips the audience check when
audiencesis unset; Istio accepts the service name when its list is empty. Both look like working validation in testing. - Token-optional meshes:
RequestAuthenticationdeployed without a policy requiring principals, so unauthenticated requests pass straight through. - One audience for everything: a single "api" audience shared by all services, which makes internal replay trivial.
- Introspection cache longer than revocation expectations: revoked tokens keep working for the cache lifetime.
- Gateway credentials left broad: the gateway's own client credentials or admin keys grant more than routing needs. See How to audit API gateway keys and usage plans.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST CSF 2.0 | PR.AA-03, PR.AA-05 | Authenticating users and services; enforcing least-privilege authorisations |
| NIST SP 800-53 Rev 5 | AC-3, AC-6, IA-9, SC-23 | Access enforcement in each service, least privilege, service identification and authentication, session authenticity |
| OWASP API Security Top 10 2023 | API1, API2, API5 | Object authorisation in services, authentication at both layers, function authorisation at the route |
| MITRE ATT&CK v20 | T1528, T1550.001 | Theft of application access tokens and their reuse for lateral movement |
| NIST SP 800-207A | Identity-based segmentation | Service identity and per-request policy instead of network location |
Practitioner checklist
- Every backend is reachable only through the gateway or mesh, verified by a direct-call test.
- Gateway and services use the same validator settings: algorithm allow list, exact issuer, skew and JWKS caching.
- Each API has a distinct audience, and services reject tokens for other audiences.
- Services reject requests with no token rather than treating them as anonymous.
- Identity headers are stripped at the edge and accepted by services only from an authenticated gateway identity.
- Deep call chains use token exchange or short-lived internal tokens, not forwarded user tokens.
- Object and tenant checks are implemented in services, including for internal callers.
- Revocation needs are documented per API and matched with local validation, introspection or cache settings.
- Validation failures are logged at both layers with reason codes and correlated.
Standards and references
- IETF: RFC 9068 JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens
- IETF: RFC 8725 JSON Web Token Best Current Practices
- IETF: RFC 9700 Best Current Practice for OAuth 2.0 Security
- IETF: RFC 8693 OAuth 2.0 Token Exchange
- IETF: RFC 7662 OAuth 2.0 Token Introspection
- NIST: SP 800-207A A Zero Trust Architecture Model for Access Control in Cloud-Native Applications in Multi-Cloud Environments
- OWASP: Microservices Security Cheat Sheet
- Istio: RequestAuthentication reference
Related NHI Mgmt Group resources: API Gateway Security Guide · JWT Security Guide · Microservices Security Guide · OWASP API Top 10 API2 Broken Authentication Guide · Service Mesh Security Guide