Token Relay
Table of Contents
This page defines the service architecture pattern
Token Relay in the pattern language.
A service forwards its caller's token to the services it calls, so each one acts
as the caller with the caller's tenant, party and permissions. In ORE Studio the
caller's token travels in the X-Delegated-Authorization header, and every HTTP
route relays the caller's token.
1. Also known as
- On-Behalf-Of (OBO) delegation, the name ORE Studio used first, and the name of Microsoft's flow.
- Token relay, the name of the Spring Cloud Gateway filter.
- Delegation, in RFC 8693, where the forwarded identity is the subject and the forwarding service is the actor.
2. Context
A caller asks service A for something, and A must ask service B for data that is scoped to the caller: rows of the caller's tenant and party. A has the caller's verified token.
3. Problem
How does service B answer as if the caller had asked it directly, when the request comes from service A?
4. Forces
- B must see the caller's tenant, party and permissions, not A's.
- B must still know that A relayed the call.
- A must not hold the caller's token beyond the request.
- Relaying must not need a call to IAM, or every hop costs one.
5. Solution
A sends its own token in Authorization, and the caller's token in
X-Delegated-Authorization. B verifies both. Its request context takes the
identity from the delegated token, so B checks the caller's permissions and sets
the caller's tenant and party on the database session. A forwards the token for
the length of the request and keeps no copy.
Figure 1: Token Relay: service A forwards the caller's token, and service B reads the caller's rows as the caller.
Source: [[./pattern_token_relay.puml][pattern_token_relay.puml]]. Regenerate with
plantuml -tpng doc/knowledge/service_architecture_patterns/pattern_token_relay.puml.
6. Failure handling
| Concern | What the pattern does |
|---|---|
| Errors | B refuses with forbidden when the caller lacks the permission, and A passes the refusal on. A does not retry with its own identity. |
| Timeouts | The relayed call has the caller's request timeout, less what A has spent. |
| Retries | A token_expired from B means the caller's token expired during the request. A cannot renew a caller's token, so it fails the request and the caller renews. |
| Idempotency | Does not apply to the relay. The call B serves has its own rule. |
| Observability | B logs the delegated subject and the relaying service. The correlation id joins the hops. |
7. Pitfalls
- The service's identity used for a caller's data. Using Client Credentials here returns no rows and no error. See Detect and correct reversed FX spot quotes.
- A relay with no caller. A timer, a scheduled job or a workflow step has no inbound token to forward. Relaying is impossible there. See Scheduled Work Acting as a Tenant.
- A relayed token stored. A request that carries the caller's token into a persisted message, such as a workflow start, makes it a credential at rest.
- A token relayed past its lifetime. Work that continues after the request, such as a long import, outlives the caller's token. Such work needs its own identity.
The first pitfall is the reason the pattern exists, and the two calls below differ only in one header:
Figure 2: Token Relay: the same read made with the service's own identity returns no rows and no error; made with the relayed caller's token it returns the party's rows.
Source: [[./pattern_token_relay_pitfall.puml][pattern_token_relay_pitfall.puml]]. Regenerate with
plantuml -tpng doc/knowledge/service_architecture_patterns/pattern_token_relay_pitfall.puml.
8. Security
B trusts A to forward only the token of A's own caller. A cannot forge a token, because only IAM signs, but a compromised A can use every token that passes through it while the token is valid. The caller's permissions bound what A can do with it.
9. Resulting context
- Benefits
- Row-level security and the permission checks apply the caller's scope at every hop, with no call to IAM.
- Liabilities
- The work is bounded by the caller's request and the caller's token. Work that outlives the request cannot use this pattern.
10. In ORE Studio
nats_client::with_delegation(token)returns a client that addsX-Delegated-Authorization;make_request_contextprefers the delegated identity.- The generated HTTP routes call
with_delegationwith the request's bearer token, so every modelled route relays. storage_transfertakes the relayed token, so storage authorises the caller.- Service-to-service auth holds the decision rule and Service-to-Service JWT Delegation is the plan that introduced it.
11. Related patterns
- Access Token — the token this pattern forwards.
- Client Credentials — the alternative for the service's own work.
- Token Exchange — the alternative when there is no caller, or when the next hop needs a narrower token.
12. Sources
13. See also
- Service architecture patterns — the structure note that orders this pattern language, and where to read this page in it.