Classic painting used as the article cover
← Back to blog

Delegation

What On-Behalf-Of Authorization Actually Proves

Four systems describe an agent acting on behalf of a user, and all four put different things on the wire. How to read a token and decide whether the action belongs to a person.

•Oct 12, 2026•15 min
DelegationOAuthIdentity

TL;DR

An action is attributable to a person only if the credential behind it names both the person and the software that acted. Most agent tokens name exactly one of the two.

RFC 9068 is blunt about the common case: for an access token obtained through the client credentials grant, the sub value "SHOULD correspond to an identifier the authorization server uses to indicate the client application." No human is in there. RFC 8693 is the opposite arrangement: the user sits in sub and the acting party sits in a nested act claim.

The phrase "on behalf of" gets used for both, which is where the trouble starts. It appears in product copy, in Microsoft's flow name, in Google's impersonation docs and in RFC 8693, and the four of them put different things on the wire.

We are not arguing that everyone needs RFC 8693. Entra's flow reconstructs the same pair of facts from a different pair of claims, and that is fine. What is not fine is a resource server that logs sub as "the user" without checking which arrangement it is in.

So inspect one real token from your agent before you trust your audit trail. Decode it, find the subject, find the client, and check whether the audience is you. If any of the three is missing you do not have attribution, you have a log line.

Overview

Take a procurement agent at a mid-size manufacturer. It reconciles supplier statements against purchase orders, and when a line will not reconcile it opens a dispute in the supplier portal under whichever buyer owns that PO. Four months later a supplier's finance team asks who authorized a disputed credit note. The only question that matters is narrow and the answer has to come out of a log: which human does that one API call belong to?

That question has a surprising number of wrong answers available. The agent had a credential. The call succeeded. Something in the trace says "procurement-agent". None of that establishes that a named buyer authorized anything, and if the credential was a service account shared across the buying team, nothing ever will.

The exchange mechanics are covered separately in token exchange for agents, and the reason an agent carries two identities at all is covered in agent authorization vs. authentication. This post is about the inspection problem on the other side of the wire. Given a token arriving at a resource server, what can you actually conclude about the human behind it? We are going to read four systems that all use the words "on behalf of" and see what each one leaves you holding.

The claim we would defend: on-behalf-of is not a trust level, it is a shape. Either the request carries two principals that a later reader can separate, or it carries one, and no amount of logging at the agent layer repairs the difference.

What "On Behalf Of" Means on the Wire

RFC 8693 splits the phrase in two, and it is worth reading the definitions next to each other rather than in sequence. On impersonation: "When principal A impersonates principal B, A is given all the rights that B has within some defined rights context and is indistinguishable from B in that context." On delegation: "With delegation semantics, principal A still has its own identity separate from B, and it is explicitly understood that while B may have delegated some of its rights to A, any actions taken are being taken by A representing B."

The two produce the same HTTP request. Same method, same path, often the same effect on the resource. They differ only in what a reader can reconstruct afterwards, which means the choice between them is an accountability decision dressed as a token format decision.

Google's IAM documentation is unusually honest about where that leaves you. It defines the pattern as impersonation outright: "When an authenticated principal, such as a user or another service account, authenticates as a service account to gain the service account's permissions, it's called impersonating the service account." And then it tells you where the second identity went: "If a principal accesses resources while impersonating a service account, most audit logs include both their identity and the identity of the service account they're impersonating."

Read that carefully. Both identities exist, in the audit log, with a hedge on the word "most". They are not in the credential the downstream service sees. If your agent impersonates a service account and the downstream system keeps its own log, that log records the service account and the human is gone. You now need two logs and a join to answer the supplier's question, and the join key is a Google-internal request identifier rather than anything in your own data.

Impersonation is not a mistake. It is the right call when the acting party genuinely has no identity worth recording, and it is operationally simpler because nothing downstream needs to understand a new claim. But it spends your attribution, and the bill arrives during an incident review.

The act Claim, and What may_act Gates

RFC 8693's delegation arrangement is one claim. Section 4.1: "The act (actor) claim provides a means within a JWT to express that delegation has occurred and identify the acting party to whom authority has been delegated." The user remains the subject. The actor is nested inside, in its own object, with its own sub.

json
{
  "aud": "https://service26.example.com",
  "iss": "https://issuer.example.com",
  "exp": 1443904100,
  "nbf": 1443904000,
  "sub": "user@example.com",
  "act": {
    "sub": "https://service16.example.com",
    "act": {
      "sub": "https://service77.example.com"
    }
  }
}

That is Figure 6 of the RFC, and the nesting is deliberate: "A chain of delegation can be expressed by nesting one act claim within another. The outermost act claim represents the current actor while nested act claims represent prior actors." For an agent that calls another agent, we think this is the only standard OAuth claim that preserves the originating human past the first hop. What a chain does and does not guarantee across several agents is covered in agent-to-agent authorization.

The companion claim does the gating. Section 4.4: "The may_act claim makes a statement that one party is authorized to become the actor and act on behalf of another party." Without something playing that role, a token service will mint an actor token for any client that asks and can authenticate, which converts your delegation story into a privilege escalation story. The request side matches: actor_token is "a security token that represents the identity of the acting party", and actor_token_type is "REQUIRED when the actor_token parameter is present in the request but MUST NOT be included otherwise."

Resource serverToken serviceAgentResource serverToken serviceAgentsubject_token = user tokenactor_token = agent credentialgrant_type=token-exchangecheck may_act for this actortoken: sub=user, act.sub=agentrequest with exchanged tokenvalidate aud, read sub and act.sub200, log records both principals

Token service Token service checks may_act, issues token with sub=user, act.sub=agent Agent --> Resource server: validates aud, reads sub AND act.sub Log line now has two principals, separable by a later reader. -->

Figure 1 — Delegation as RFC 8693 arranges it. The point of the act claim is not the exchange, it is that the resource server's own log can name two principals without consulting anyone else's system.

What sub Alone Tells You

Here is the trap, and we think it catches more agent deployments than any exotic attack.

RFC 7519 defines the subject claim as broadly as possible: "The sub (subject) claim identifies the principal that is the subject of the JWT." It does not say the principal is a person. RFC 9068, which profiles JWT access tokens, then splits the behaviour by grant. For grants with a resource owner involved, such as the authorization code grant, "the value of sub SHOULD correspond to the subject identifier of the resource owner." For the client credentials grant, "the value of sub SHOULD correspond to an identifier the authorization server uses to indicate the client application."

Same claim, two populations. A resource server that reads sub and writes it into a column called user_id is correct half the time and silently wrong the other half, and nothing in the token flags which half you are in. The disambiguator is sitting right there: RFC 9068 makes client_id a required claim in JWT access tokens. If sub equals client_id, no human was involved in obtaining that token.

The check that costs nothing: compare sub to client_id on every inbound token. Equal means the application is the subject and your attribution stops there. Different means someone consented, and the pair of values is the two identities you need.

What the token cannot tell you is whether the action behind it was a good idea, which is a separate gap covered in why OAuth alone isn't enough for AI agents.

The audience claim is the third leg. RFC 9068 requires aud, and requires the resource server to confirm "the aud claim contains a resource indicator value corresponding to an identifier the resource server expects for itself." Audience is not an attribution claim, but it is what stops attribution from being forgeable, because a token minted for somebody else's API tells you nothing reliable about who is calling yours.

Entra's On-Behalf-Of Flow, in Its Own Terms

Microsoft owns the phrase in practice, because Entra named a flow after it. Their definition: the OBO flow "describes the scenario of a web API using an identity other than its own to call another web API. Referred to as delegation in OAuth, the intent is to pass a user's identity and permissions through the request chain."

The request is a JWT bearer grant with one distinctive parameter. grant_type is urn:ietf:params:oauth:grant-type:jwt-bearer, assertion carries "the access token that was sent to the middle-tier API", and requested_token_use "must be set to on_behalf_of". The assertion rule is the interesting one, because it is an audience rule wearing a delegation hat: that token "must have an audience (aud) claim of the app making this OBO request", and "Applications can't redeem a token for a different app (for example, if a client sends an API a token meant for Microsoft Graph, the API can't redeem it using OBO. It should instead reject the token)."

Two more details are worth lifting out. First, the flow deals only in delegated permissions: it "only uses delegated scopes and not application roles", because roles "remain attached to the principal (the user) and never to the application operating on the user's behalf. This occurs to prevent the user gaining permission to resources they shouldn't have access to." That sentence is the intersection rule stated as product behaviour, and it is the subject of agent scope and user permission.

Second, Microsoft tells you not to relay the resulting token anywhere: "DO NOT send access tokens that were issued to the middle tier to anywhere except the intended audience for the token." Their listed reasons include inability to satisfy "token binding and Conditional Access scenarios requiring claim step-up".

What the resulting token is not, is an RFC 8693 token with a nested act. Entra hands you a token whose subject is the user and whose client is the middle tier, recorded in the client identity claims rather than in an actor object. Both facts are there. They are in a different place. So a resource server that validates "two principals present" by looking for act will reject a perfectly well-formed Entra OBO token, and an agent platform that advertises on-behalf-of without saying which arrangement it emits has told you nothing checkable.

Why MCP Forbids Passing Through What You Were Handed

The MCP 2026-07-28 authorization specification is the strictest writing on this we have read, and it gets there from the audience side rather than the attribution side. An MCP server is formally an OAuth 2.1 resource server, and the rules are stacked: servers "MUST validate that access tokens were issued specifically for them as the intended audience"; servers "MUST only accept tokens that are valid for use with their own resources"; servers "MUST NOT accept or transit any other tokens."

Then the part that matters for an agent sitting between a user and three SaaS APIs: "If the MCP server makes requests to upstream APIs, it may act as an OAuth client to them. The access token used at the upstream API is a separate token, issued by the upstream authorization server. The MCP server MUST NOT pass through the token it received from the MCP client."

The companion security guidance explains the cost of ignoring that, and the reasons are accountability reasons rather than confidentiality ones. The server "will be unable to identify or distinguish between MCP Clients when clients are calling with an upstream-issued access token which may be opaque to the MCP Server", and "the downstream Resource Server's logs may show requests that appear to come from a different source with a different identity, rather than the MCP server that is actually forwarding the tokens." Both, it says, "make incident investigation, controls, and auditing more difficult."

no

yes

yes

no

act.sub present

client claims only

no

Inbound token at the gateway

aud names this service?

401, reject

sub equals client_id?

Application is the subject

acting party named?

Delegation: two principals

OBO shape: two principals

User token, no actor recorded

Mint a separate upstream token

Call upstream, log both principals

Figure 2 — The inspection order. Audience first because it is the only check that can be forged past, then subject against client, then the acting party. The last step is the same in every branch.

The practical reading is that a gateway acting for an agent needs two tokens per call and has to resist the temptation to use one. The MCP permissioning gap more broadly is covered in the MCP OAuth permissioning problem.

Reading Attributability Off a Token

Put the four systems in one table and the inspection procedure writes itself.

ArrangementSubject claimActing partyAttributable to a person?
Client credentials (RFC 9068)the client applicationnot applicableNo
Authorization code, user tokenthe resource ownernot recordedYes, but the software is unnamed
RFC 8693 delegationthe resource ownernested act.subYes, both named in the token
Impersonation (8693 or Google IAM)the impersonated identityin the provider's audit logOnly by joining two logs

The procedure is four checks, in this order. Validate aud against your own identifier and reject otherwise. Compare sub to client_id; if they match, stop calling this a user action. Look for an acting party, in act.sub or in the client identity claims, and record whichever you find under a field name that says which one it was. Then write both principals into your own log rather than relying on the provider's.

This is the delegation model dimension of the Agent Access Control Taxonomy, and the vocabulary is useful precisely because "on behalf of" is overloaded. The taxonomy's aact:delegation-model/on-behalf-of-user is defined as an agent that "acts for a specific human, and its effective permissions are bounded by that human's permissions", and it explicitly includes both OAuth authorization code grants and "token exchange with an actor claim" as ways of getting there. aact:delegation-model/autonomous is the other case, where the agent "acts under permissions granted directly to it, not derived from any human user", and that is the honest label for a client credentials deployment. aact:delegation-model/agent-to-agent carries the risk note that matters here: without a recorded principal chain, "the originating human is lost after the first hop."

Write the label down before the incident, not after. A deployment that cannot say which of those three terms it implements is not going to discover the answer under time pressure with a supplier on the phone.

Frequently Asked Questions

What is on-behalf-of authorization? It is the pattern where a service makes a call for a user and the call carries both identities, so a later reader can tell which human the action belongs to and which piece of software performed it. RFC 8693 calls that delegation and expresses it as a sub plus a nested act claim. Microsoft calls it the on-behalf-of flow and expresses it as a user subject plus the middle tier's client identity.

What is the difference between delegation and impersonation in OAuth? RFC 8693 draws it in one sentence each. Under impersonation the acting party "is indistinguishable from" the user in that context. Under delegation the acting party "still has its own identity separate from" the user. Impersonation is simpler to deploy because nothing downstream needs to understand an extra claim, and it costs you the ability to attribute the action without a second log.

What is the act claim in a JWT? The actor claim from RFC 8693 section 4.1. It names the party to whom authority was delegated, nested inside the token whose subject is the user. Nesting one act inside another expresses a chain, with the outermost being the current actor.

Does a client credentials token identify a user? No. RFC 9068 says its sub should indicate the client application. If your agent authenticates with client credentials and you log sub as a user identifier, you are logging the application name in a column labelled "user".

Is Microsoft's on-behalf-of flow the same as RFC 8693 token exchange? No. It is a JWT bearer grant with requested_token_use=on_behalf_of, not the token exchange grant type, and the resulting token does not carry a nested act claim. The two achieve similar accountability from different claim layouts, which is exactly why you cannot write one validator and assume it covers both.

How do I know if an agent action is attributable to a person? Decode the token the resource server actually received. If the audience is not you, nothing else matters. If sub equals client_id, the answer is no. If sub is a person and you can also name the software, from act.sub or from the client claims, the answer is yes and you should be writing both into your own audit record.

Can an agent forward the token it was given to a downstream API? Under MCP's 2026-07-28 authorization spec it must not. The server "MUST NOT pass through the token it received from the MCP client", and needs a separate token issued by the upstream authorization server. The stated reasons are audience validation and the audit confusion that passthrough creates downstream.

Conclusion

The reason this matters more for agents than for ordinary service-to-service calls is volume and distance. A human clicking a button in a SaaS app generates an action with a session behind it. An agent generates thousands of actions whose only link to a person is whatever the credential carried at the time, and the question "who authorized this" arrives months later from someone who does not care about your architecture.

Everything above reduces to one habit. Treat "on behalf of" as a claim about the token's shape, go look at the shape, and name it using a vocabulary that distinguishes the cases. The specs have done the hard part already: RFC 8693 defines the two semantics and the claim that expresses the gentler one, RFC 9068 tells you what sub means in each grant, Entra documents its own layout, and MCP tells you not to launder identity by forwarding tokens.

What none of them will do is inspect your deployment for you. The fail-closed question, what happens when the authorization decision cannot be made at all, is a different dimension and is covered in fail-closed agent authorization.

One token, read out loud. Pull a real token from a real agent call, decode it, and say the sentence "this action is attributable to X, performed by Y, for audience Z." If you cannot finish the sentence, your audit trail cannot either.

Sources

All URLs read 2026-10-12.