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.

pattern_token_relay.png

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:

pattern_token_relay_pitfall.png

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 adds X-Delegated-Authorization; make_request_context prefers the delegated identity.
  • The generated HTTP routes call with_delegation with the request's bearer token, so every modelled route relays.
  • storage_transfer takes 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

Emacs 29.3 (Org mode 9.6.15)