All articlesProtocols

The agent protocols are standardised. Your authorization isn't.

A2A now sits beside MCP at the Agentic AI Foundation, and both leave authorization to you. Delegation versus impersonation, token exchange at every hop, and the RFC 8693 rule that decides where chain policy has to live.

Sep 12, 202612 min read

The agent protocols are standardised. Your authorization isn't.

In August, the Agent2Agent protocol was accepted as a Growth Stage project at the Linux Foundation's Agentic AI Foundation, where it now sits beside MCP.1A2A Protocol · Aug 27, 2026A New Chapter for A2A: Joining the Agentic AI Foundation Open source (opens in a new tab) The two protocols that define how agents reach tools and how agents reach each other share a single neutral steward. For anyone who spent 2025 hedging between competing specs, that is genuinely good news.

It's also a good moment to be precise about what a protocol standard settles, because the thing most enterprises actually need to decide — what an agent may do on a person's behalf, three hops away from that person — is not in it. Not as an oversight. By design.

What the spec says it leaves to you

You don't have to infer this. The A2A documentation on enterprise readiness states it directly. Protocol payloads "don't carry user or client identity information directly. Identity is established at the transport/HTTP layer."2A2A Protocol documentationEnterprise-ready featuresAuthentication and Authorization sections Open source (opens in a new tab) And once a client is authenticated, "the A2A server is responsible for authorizing the request. Authorization logic is specific to the agent's implementation, the data it handles, and applicable enterprise policies."2A2A Protocol documentationEnterprise-ready featuresAuthentication and Authorization sections Open source (opens in a new tab)

That's the right call for a protocol. Authorization policy depends on your data classification, your contracts, your regulators and your org chart, and no specification can know any of those. But it means there are two gates, and the standard only builds one of them:

  • Authentication — is this caller who it claims to be? The agent card declares the security schemes it accepts, and the caller proves identity before sending a message. We covered that mechanism in A2A in production.
  • Authorization — given who the caller is, may it reach this record, for this user, as part of this task? That gate is yours to build, and it's where multi-agent systems get into trouble.

The problem shows up at the third hop

With one agent and one user, authorization is familiar. The user signs in, their identity is forwarded into every downstream call, and your services enforce the same per-user access they always have — the pattern in letting an agent act as you, safely.

Now build the system people actually want. A user asks the orchestrator for something. The orchestrator delegates part of it to a specialist. The specialist needs data held by a partner's agent, in another organisation, behind its own agent card. The request crosses three agents and two companies, and at the far end one question has to be answered correctly: whose authority is this?

The card on that partner agent tells you how the specialist proves it is a trusted agent. A client-credentials flow authenticates the calling agent. It says nothing about the person that agent is working for. By the third hop, the natural implementations have quietly collapsed into one of two bad options.

Two answers that seem fine and aren't

Option one: each agent acts as itself. Every agent has a service identity with whatever access its job might plausibly need, and the partner authorizes the calling agent. This is the confused deputy in its purest form. The partner can no longer tell a request for one user's records from a request for every user's records, because both arrive from the same agent with the same rights. Whatever constrains the agent to a single user's data lives in a prompt — and no prompt is a security boundary.

Option two: forward the user's token everywhere. This is the pattern that works beautifully for one hop inside your own trust boundary, which is exactly why it gets extended past where it belongs. Pass the user's access token from orchestrator to specialist to partner, and every service authorizes the real user. Correct identity, at every hop.

And now every hop holds a bearer credential carrying all of the user's rights, valid for every audience that token was issued for, for its full lifetime. The partner's agent — which you do not operate, cannot audit and did not write — can present it anywhere it's accepted. So can any agent in the chain that reads a hostile document and gets steered into sending it somewhere, which is not hypothetical: planted instructions aimed at agents are already on the open web. The more hops a token crosses, the more places it can leak from, and each one leaks the whole thing.

Forwarding identity is correct. Forwarding the credential that carries it, unchanged, to parties you don't control, is how one compromised agent inherits everything the user can do.

Delegation, not impersonation

OAuth solved the shape of this years before agents needed it, in RFC 8693, Token Exchange. Its first contribution is vocabulary, and the vocabulary is the whole argument. Under impersonation, the RFC says, one principal "is given all the rights that B has within some defined rights context and is indistinguishable from B." Under delegation, the acting principal "still has its own identity separate from B," and "any actions taken are being taken by A representing B."3IETFRFC 8693: OAuth 2.0 Token Exchange§1.1 (delegation vs. impersonation), §4.1 (the act claim), §4.4 (the may_act claim) Open source (opens in a new tab)

Forwarding a user's token unchanged is impersonation. Every hop is indistinguishable from the user. What a multi-agent system needs is delegation: each hop is identifiably itself, acting for the user, with a narrower slice of the user's authority.

Mechanically, each hop exchanges the token it received for a new one before calling the next agent. The new token is audience-restricted to exactly that next agent, scoped down to what the task requires, and short-lived:

goagent/delegation/exchange.go
// At each hop, trade the token you received for one that is only good for
// the next hop. The incoming token never leaves this process: the next agent
// receives a credential that names it as the audience, carries only the
// scopes this task needs, and expires in minutes.
func (h *Hop) tokenFor(ctx context.Context, incoming string, next AgentID, task Task) (string, error) {
    form := url.Values{
        "grant_type":           {"urn:ietf:params:oauth:grant-type:token-exchange"},
        "subject_token":        {incoming},
        "subject_token_type":   {"urn:ietf:params:oauth:token-type:access_token"},
        "actor_token":          {h.selfAssertion(ctx)}, // proves which agent is acting
        "actor_token_type":     {"urn:ietf:params:oauth:token-type:jwt"},
        "audience":             {string(next)},
        "scope":                {strings.Join(task.RequiredScopes(), " ")},
        "requested_token_type": {"urn:ietf:params:oauth:token-type:access_token"},
    }
    tok, err := h.sts.Exchange(ctx, form)
    if err != nil {
        return "", fmt.Errorf("token exchange for %s: %w", next, err)
    }
    return tok.AccessToken, nil
}

The token that arrives at the partner's agent is useless anywhere else. It can't be replayed against a different service because the audience won't match, it can't do more than read the ledger because that's all it carries, and it expires before anyone could do much with it if it leaked. The RFC's act claim records who is acting, and exchanges can nest it to record how the request got there:

jsontoken claims at the partner agent
{
  "sub":   "user-4821",
  "aud":   "agent://reconciliation",
  "scope": "ledger.read",
  "exp":   1757689200,
  "act": {
    "sub": "agent://orchestrator",
    "act": {
      "sub": "agent://intake"
    }
  }
}

Read top-down: the subject is still the user, the audience is only the reconciliation agent, the scope has narrowed to one permission, and the current actor is the orchestrator, which in turn was acting for intake.

The detail that trips everyone up

That nested chain is tempting. It looks exactly like what you'd want to write a policy against — only accept requests that passed through our intake agent, say, or reject anything that touched an agent outside our organisation.

The RFC tells you not to. Section 4.1 is explicit: for the purpose of access control, the consumer of a token "MUST only consider the token's top-level claims and the party identified as the current actor by the act claim. Prior actors identified by any nested act claims are informational only and are not to be considered in access control decisions."3IETFRFC 8693: OAuth 2.0 Token Exchange§1.1 (delegation vs. impersonation), §4.1 (the act claim), §4.4 (the may_act claim) Open source (opens in a new tab)

That's a deliberate design choice, and it tells you where chain policy has to live. A resource server at the end of the chain can see who is acting now and on whose behalf. It cannot be the place that enforces how the chain was allowed to form. That decision belongs to the token service, at the moment each link is minted — the only point where a rule about the next edge can actually be refused:

gosts/policy.go
// Prior actors in a token are informational only (RFC 8693 §4.1), so a
// resource server cannot safely enforce "allow only if this came through
// intake". Rules about the shape of the chain have to be enforced where the
// chain is built: at the token service, every time it is asked to extend it.
func (p *Policy) mayExchange(subject Claims, actor, audience AgentID, scopes []string) error {
    if !p.mayActFor(subject.Sub, actor) {
        return status.Error(codes.PermissionDenied, "actor may not act for this subject")
    }
    if !p.edges.Allowed(actor, audience) {
        return status.Error(codes.PermissionDenied, "no delegation path from actor to audience")
    }
    if !isSubset(scopes, subject.Scopes()) {
        return status.Error(codes.PermissionDenied, "exchange cannot widen scope")
    }
    if chainDepth(subject) >= p.maxHops {
        return status.Error(codes.PermissionDenied, "delegation chain too long")
    }
    return nil
}

Four rules carry most of the weight. The actor has to be permitted to act for this subject at all, which is what the RFC's may_act claim expresses.3IETFRFC 8693: OAuth 2.0 Token Exchange§1.1 (delegation vs. impersonation), §4.1 (the act claim), §4.4 (the may_act claim) Open source (opens in a new tab) There has to be an allowed edge from this actor to this audience, so your delegation graph is declared rather than emergent. The exchange may narrow scope and must never widen it. And chains have a maximum depth, because an unbounded chain is a loop waiting to happen.

"Exchange cannot widen scope" deserves emphasis. It's the invariant that makes the whole design hold: however many hops a request crosses, authority only ever decreases. No agent in the chain can end up holding more than the user started with, and most will hold far less.

Authorizing the request isn't trusting the answer

Token exchange settles what a downstream agent may reach. It says nothing about whether what comes back is true. A partner agent that was perfectly entitled to read the ledger can still return a wrong reconciliation — mistaken, stale, or steered by something it read.

So treat another agent's output the way you'd treat a web page: as untrusted input to your turn, not as a fact your system now knows. Validate it against a schema. Mark the turn accordingly before it reaches any consequential tool. And when the result drives a decision, record which agent asserted it. Authorization is about access; trust is about provenance, and a multi-agent system needs both.

The chain is your audit record

There's a payoff to doing this properly that justifies the work on its own. Every exchanged token carries the user, the current actor, the prior actors and the scope that was granted at each hop. Log the exchange alongside the task id and you have the answer to the question every compliance team eventually asks — which agent did what, for whom, on whose authority? — as a query rather than an investigation. The nested actors are informational for access control. For your audit trail, they're the whole story.

The pipes are shared. The policy is yours.

A single foundation stewarding both agent protocols is what makes cross-organisation agent systems realistic. It standardises how agents discover each other, authenticate and hand off work. It deliberately doesn't standardise whose authority flows through those pipes, because that was never a protocol's decision to make.

So make it deliberately. Forward identity, never raw credentials. Exchange at every hop. Narrow scope monotonically, enforce the chain where tokens are minted, and treat every other agent's answer as a claim. The protocols got standardised this summer. The authorization was always going to be your job.

Sources

  1. A2A Protocol documentation

    Enterprise-ready features (opens in a new tab)

    Authentication and Authorization sections

  2. IETF

    RFC 8693: OAuth 2.0 Token Exchange (opens in a new tab)

    §1.1 (delegation vs. impersonation), §4.1 (the act claim), §4.4 (the may_act claim)

Thinking about an agent like this for your team?

Describe the job you want automated and our Automation Architect blueprints it in seconds — or talk it through with the engineers who ship them.