When a user request fans out across internal services, the authorization context derived at the edge has to travel with it. If it does not, a downstream service sees only the broadly trusted service that called it and performs the action without the user's restrictions. This post covers how to carry the user's identity and permissions through the chain.

System description

A user request crosses several internal services before it touches data. The external token stops at the edge service, which exchanges it for a short-lived internal token carrying the user and tenant. Each downstream service verifies the calling workload and authorizes its own action against that context.

Preserving User Authorization Across Services

Architecture choice

There are two common ways to carry the user through the chain: exchange a new token at each hop, or mint one assertion at the edge and forward it.

Exchange per hop

Each service trades the token it received for a new one scoped to the service it is about to call. The token service checks the caller and the presented token on every exchange. RFC 8693 token exchange is the standard form; hosted identity platforms ship the same gate under names like on-behalf-of.

Use this when:

  • Hops need different scopes, and each service should hold a token that works only for its own next call

  • Revocation and exchange policy need one enforcement point

  • You already run an identity provider or STS that supports token exchange

Trade-off: the token service joins the critical path of every internal call. Its latency and availability now bound the chain's.

Edge-minted assertion

The edge mints one short-lived, integrity-protected identity assertion per request. Services forward it unchanged and verify it locally with distributed keys. Netflix's Passport and Google's end-user context tickets are production examples of this shape.

Use this when:

  • Requests fan out across many hops and an exchange round-trip per hop costs too much

  • The context downstream services need is identity and tenant, and per-hop scoping would add little

  • Local verification fits your platform better than a highly available token service

Trade-off: one artifact is valid for its whole lifetime at every service that trusts the edge, so there is no per-hop narrowing. Keep the lifetime to the life of the request.

Common middle ground: exchange at the edge and at boundaries between trust tiers, and forward the assertion unchanged between services inside one tier

The rest of this post assumes exchange per hop.

Golden path

Edge service authenticates the user and authorizes the request → exchanges the external token for a short-lived internal token carrying the user and tenant → downstream service verifies the calling workload and the token audience → authorizes the action against the user's context → calls the next hop with a narrower token → audit log records both the user and the service that acted

Related patterns:

Core design

Internal token

A short-lived JWT the token service mints for one hop. Minimum claims:

  • sub: the user the work is being done for

  • tenant_id: the tenant that owns the resources in scope

  • act: the workload currently acting for the user, in the RFC 8693 actor-claim shape; prior hops nest inside it as history

  • aud: the one service meant to accept the token

  • scope: the operations the token covers at that service

  • exp: an expiry in minutes, no later than the expiry of the token presented to mint it

  • txn: a correlation id minted at the edge and copied into every token in the chain

The receiving service authorizes against the top-level claims and the current actor. Nested prior actors are recorded as history only.

Token service

The only component that mints internal tokens. An exchange request carries the token the caller received and the caller's own workload credential, and the service validates both before issuing. Which workloads may exchange, and for which audiences and scopes, is exchange policy: stored with the token service, versioned, and evaluated on every request.

Two identities at every hop

An inbound internal call carries two identities. The workload credential says which service made the call; the internal token says which user and tenant the work is for. The receiving service checks both, and the caller's workload identity must match the token's current actor. It then authorizes the action against the token's user and tenant. Each hop repeats these checks, and each exchange hands the next hop equal or narrower authority.

Deferred work

Work that outlives the request does not keep the request's token. The job record stores the user, the tenant, and the txn id. At execution the worker presents its own workload credential and the job id. The user and tenant come from the job record, not from the worker's request, and exchange policy runs at that moment.

Audit record

Every hop logs the action with the token's subject, the current actor, and the txn id. The edge writes the same txn id on its record of the external request. A query on one txn value returns the user-facing request and every internal action behind it.

Security properties of the internal token

The internal token binds:

  • The user and tenant the work is for (sub, tenant_id)

  • The workload acting for them (act)

  • One receiving service (aud)

  • A scope and a lifetime measured in minutes

It does not guarantee:

  • That the presenting workload is the one named in act

  • That the user still holds access at use time

  • That every service checks it

  • That scope narrowed at the exchange; the format carries whatever scope the token service issued

Threat model

Baseline assumptions

  • Internal callers are semi-trusted: any workload that can reach a service can send it requests with any headers and any tokens it holds. Reaching a service over the internal network is not authentication

  • Services can verify each other's workload identity through mTLS or platform credentials; forging workload identity itself is out of scope here

  • The edge derives user and tenant from a verified external credential, not from request fields

  • Services hold the token service's public keys and can verify internal tokens locally

  • Standard infra controls such as TLS, secret management, and network segmentation are assumed to be in place. This model focuses on carrying user authorization across internal hops

A note on risk

This table is not a checklist. Focus on preventing the highest-impact failures first. Detection and response are acceptable where prevention is impractical.

Phase 1: Minting and exchange

Focus: Controlling who can turn one credential into another

Asset

Threat

Baseline Controls

Mitigation Options

Risk

Token service

Anonymous exchange: The exchange endpoint validates the presented token but not the caller, so any holder of a stolen token can trade it for fresh tokens aimed at other services

Subject token validation

1. Client authentication: every exchange requires the calling workload's own credential

2. Audience gate: accept a presented token only from a caller named in its aud

3. Alerting: flag exchange requests from workloads outside the token's expected call path

Medium

Exchange policy

Scope widening: The token service issues whatever scope and audience the caller requests, so a hop can ask for more authority than the token it presented

Exchange requires client authentication

1. Scope mapping: exchange policy caps what each incoming scope may become at the target service, and requests outside the map are refused

2. Per-caller policy: allowlist which workloads may mint for which audiences

3. Explicit response: return the issued scope so callers cannot assume they got what they asked for

Medium

External token

Interior exposure: The external access token rides the whole chain and lands in logs and traces deep in the stack

TLS on internal calls

1. Terminate at the edge: the exchange consumes the external token; internal calls carry only the internal one

2. Never embed: the internal token must not contain the external one

3. Log scrubbing: redact bearer credentials from internal logs and traces

Medium

Availability

Fail-open fallback: The token service is down, and callers fall back to plain service-to-service calls to keep traffic moving, so user-scoped requests run without user context

None

1. Fail closed: user-scoped operations return errors while no internal token can be minted

2. Outage drills: take the token service down in staging and verify the chain degrades to errors

3. Detection: alert on tokenless calls reaching user-scoped endpoints

High

Phase 2: The call chain

Focus: Keeping both identities checked at every hop

Asset

Threat

Baseline Controls

Mitigation Options

Risk

User identity

Header trust: A service reads the acting user from a plain request header gated only by network position, so any caller that can reach the service sets the header and impersonates any user

None

1. Signed context only: user identity travels in the signed internal token, and plain identity headers are ignored

2. Strip at boundaries: the edge and every proxy overwrite reserved identity headers on inbound requests

3. Authenticated asserter: where a proxy or sidecar must assert identity in a header, accept it only over that proxy's authenticated connection, and keep the app port unreachable any other way

Medium

Downstream data

Confused deputy: The receiving service authorizes the calling service and never re-evaluates the user, so a crafted request from any authenticated user rides the trusted service's standing privilege to data that user was never allowed to see

Workload authentication

1. Require user context: user-scoped operations demand a valid internal token, with the resource checked against its user and tenant

2. Deprivileged service accounts: strip standing data access from service identities so authority arrives with the token

3. Per-request re-check: services that proxy or fetch on demand authorize each induced request instead of reusing an approved channel

High

Internal credential

Cross-service replay: One internal token is accepted by many services, so a compromised hop holds a credential that works at every other

Short TTL

1. Audience per hop: each token names one recipient, and every service rejects tokens not addressed to it

2. Exchange per hop: calls to the next service use a new token minted for it

3. Possession binding: bind the token to the caller's mTLS identity so a copied token fails without the caller's key

Medium

Phase 3: Deferred work and audit

Focus: Acting for the user after the request ends, and proving who acted

Asset

Threat

Baseline Controls

Mitigation Options

Risk

Deferred job

Stale authority: The job stores a bearer token at enqueue time and presents it at execution, so a revocation in the gap is invisible to a verifier that checks signature and expiry

Token expiry

1. Re-mint at execution: store user and tenant references on the job and exchange for a fresh token when it runs

2. Issue-time cutoff: reject tokens minted before the user's most recent revocation event

Medium

Audit trail

Actor-only logs: Each hop logs the service credential that called it, so an investigation sees the deputy on every line and cannot reconstruct which user drove the action

Per-service request logs

1. Both identities: every action records the token's subject and the current actor

2. Chain correlation: one txn id from the edge joins every downstream log line

3. Edge anchor: the edge record maps txn to the external request and user session

Low

If you forward the external token instead

If every internal service accepts the token the client presented at the edge, the trade-offs shift:

  • The token's aud names the public API. Internal services that enforce the audience check reject it, so in practice the check gets dropped

  • A compromise of any one service yields a credential valid at all the others and at the public API itself

  • Every service parses the external identity provider's token format and trusts its keys; swapping providers later means changing every internal service

  • Revocation can be centralized: one token, revoked at the issuer, and hops that introspect it see the revocation. The cost is an introspection dependency on the external issuer at every hop

FAQs

Why can't internal services trust an X-User-Id header?

Nothing about arriving on the internal network proves the header was set by your edge; any workload that can open a connection to the service can send the same header with any value. If user context must travel in a header, sign the value and verify it at the consumer, which turns the header into a token.

What identity should a background job run with?

Store the user and tenant on the job record and mint a fresh internal token when the job executes, so authorization reflects the user's access at run time. A job that carries the enqueue-time token is presenting a decision that may have been revoked since. Work that is genuinely not user-scoped, like a nightly rebuild, should run under the service's own identity and be logged as service work.

Verification checklist

  • Token contents

    • Every internal token carries sub, tenant_id, act, aud, scope, exp, and txn

    • The external access token appears nowhere inside an internal token or a job payload

    • An exchange never issues a token that expires after the one presented to mint it

  • Exchange

    • An exchange request without a valid workload credential is refused

    • A workload not named in the presented token's aud cannot exchange it

    • Requesting a scope outside the exchange policy's mapping for the presented token returns a narrowed grant or a refusal, and the response states the issued scope

    • A token minted for service B is rejected by service C

  • Per-hop enforcement

    • A call with a valid workload identity and no internal token cannot perform user-scoped operations

    • A token presented by a workload other than its current act is rejected

    • Plain identity headers on inbound requests are overwritten at the edge and ignored by services

    • A service that hands token checks to a sidecar cannot be reached around it; a request straight to the app port fails

    • A token for tenant A's user addressing tenant B's resource gets a 404

    • With the token service unavailable, user-scoped calls fail rather than proceed on workload identity alone

  • Deferred work

    • Revoking a user's access between enqueue and execution makes the job fail authorization when it runs

    • Job records store user and tenant identifiers; queue storage contains no bearer tokens

    • A worker cannot mint a token by supplying user and tenant values that no job record contains

  • Audit

    • Every action's log line carries the token's subject and the current actor

    • One txn id query returns the edge request and every internal action behind it

    • The log distinguishes a service acting for a user from the same service acting for itself

Implementation & Review

The full threat model matrix, architectural diagrams, and a printable verification checklist for this pattern are available in the Secure Patterns repository. Use these artifacts to guide your design reviews and internal audits.

Keep Reading