Diese Notiz ist noch nicht übersetzt — die englische Version wird angezeigt.

Acting on a Human's Behalf

Put a service between a person and a backend and you've created an identity question, whether or not you notice. The person authenticated to your service. Your service now has to say something to the backend. What, exactly?

There are two credentials in play here, and the entire design rests on keeping them apart:

  1. The one the caller presented to your service.
  2. The one your service presents to the backend.

The tempting move — and the one worth naming as a mistake up front — is to make the second a copy of the first.

Why passing the token through breaks things

If a service simply forwards the caller's token to the backend, the backend can no longer tell whether a request arrived through the service or directly. Both look identical on the wire, because they are identical.

That sounds abstract until you list what depends on being able to tell:

  • Rate limits scoped per client stop meaning anything, because there's only one apparent client.
  • Audit trails record the human, with no record that a service was involved at all.
  • Anomaly detection loses its baseline: traffic from an automated component and traffic from a person at a keyboard have very different shapes, and merging them hides both.
  • Revocation gets coarse. You can no longer cut off the service without cutting off the person.

Every one of those controls is keyed on the token's audience — who the token was minted for. Passthrough destroys that field's meaning. This isn't a policy preference; it's why the MCP specification forbids it outright rather than merely discouraging it.

So the service needs its own credential. That leaves exactly two shapes.

Option one: service identity

The service holds a credential of its own. It authenticates to the backend as itself.

The backend sees the service, cleanly and unambiguously. Audience checks work. Rate limits work. Revoking the service is one operation.

The cost is stated plainly: the human's trail ends at the service. The backend knows "this service did it", not "this service did it for Amir". If you need to answer which person triggered a given backend call, you have to answer it from the service's own logs, and you have to trust those logs, and they're now a separate system from your backend's audit trail.

This option's real virtue is that it works everywhere. Every OAuth server does client_credentials. There is no feature to shop for.

Option two: delegation

The service exchanges the caller's token for a different token, minted for the backend, that carries both identities.

RFC 8693 (OAuth 2.0 Token Exchange) is the standard for this, and its mechanism is the act claim: the subject of the token is the human, and the actor is the service. Both are present in one token, and a backend that reads the claim sees "X, acting through Y." Whether a given backend reads it is a separate question, taken up further down — the answer is not automatically yes.

The act claim nests. A chain of services each add themselves, and the resulting token describes the whole path rather than flattening it.

Delegation is not impersonation

This is the distinction most worth carrying away, because the two are frequently discussed as one thing and they have opposite properties.

  • Impersonation: the service becomes the human. The resulting token says "X". The service's involvement is gone — not obscured, gone. Downstream, there is no way to recover the fact that a machine was in the path.
  • Delegation: the service acts as itself, on behalf of the human. The resulting token says "X, via Y". Both identities survive.

Mechanically, the difference between them is a single parameter: whether the exchange request includes an actor_token. Send it, and you get delegation. Omit it, and you get impersonation.

That's a startlingly small lever for a large semantic difference, and it's worth knowing which one a given implementation gives you by default — and, more to the point, whether it supports both at all. Several do not.

But does the backend read it?

Putting the identity into the token and having something downstream act on it are different achievements, and the standard only guarantees the first.

This got measured rather than assumed: Zitadel 2.71.7 issuing tokens against Stalwart 0.16.17. The exchange itself works exactly as described above — sub is the human, act.sub is the service, and Stalwart accepts the token and acts as the human.

Then it stops. Stalwart never reads act. And a successful bearer authentication produces no authentication event at all — checked with a trace-level tracer — where a password login produces one. So on the backend side, a delegated request is indistinguishable from the person acting directly, and neither one is written down.

Worth being precise about what that is and isn't. It's not a defect, and it isn't carelessness: reading act is a capability that simply isn't present, which is the ordinary state of most backends today. The RFC defines what a token may say. It obliges no resource server to care.

Delegation puts the identity in the token. Whether the backend reads it is a second question — and it often isn't asked.

The consequence lands directly on the choice this page is about. If you picked delegation to get an end-to-end audit trail, you've done half the work. The other half is confirming the backend uses what you sent. Where it doesn't, the trail has to be produced by your own service instead — which is precisely the cost option one was already charged for above.

So delegation's advantage over service identity is smaller than the earlier sections imply, whenever the backend ignores act. What survives that discount is still real:

  • Short-lived tokens rather than a long-lived shared secret sitting in the service's configuration.
  • Central revocation — cut the service off at the issuer instead of rotating a credential everywhere it was deployed.
  • No backend credential in the service's config at all.

Those are security properties, not audit properties. If audit was the reason for reaching for delegation, check the far end before counting the win.

The questions this actually turns into

Notice what happened. "How should my service authenticate?" resolved into two much more specific questions, both answerable before committing to anything:

  1. Does my token issuer support RFC 8693 with an actor_token?
  2. Does my backend do anything with the act claim once it arrives?

The first is a yes/no you can settle from documentation — and plenty of identity providers answer no while still advertising RFC 8693 support, because issuing a token for yourself and issuing a token that names two parties are different features filed under one RFC number. That's the subject of the next note: Questions to Ask an IdP.

The second is the one that's easy to skip, because the standard says nothing about it and the token looks correct either way. It can only be answered against the specific backend — and, as above, the answer is often no.

Where this actually stands

Budding. The model is settled and the vocabulary is standard — none of it is invented here. One end of it has since been measured rather than read: the Zitadel/Stalwart result above is a real test of a real pair, not a documentation claim. The deployment itself still hasn't happened, so most of the operational story is still ahead.

The honest summary, updated: service identity is always available and gives up the human's trail. Delegation preserves that trail in the token, and constrains your choice of issuer considerably — but it only pays off in audit terms if the backend reads what it's sent. Pick knowing which you're paying for, and check the far end before assuming you got it.