AI Agent Identity and Delegated Authorization: OAuth Token Exchange, On-Behalf-Of Flows and Workload Identity for Agents (2026)

AI Agent Identity and Delegated Authorization: OAuth Token Exchange, On-Behalf-Of Flows and Workload Identity for Agents (2026)

AI Agent Identity and Authorization: Token Exchange, OBO and Workload Identity

Most production incidents involving AI agents and credentials share one root cause: the agent was handed a credential that answered the wrong question. A long-lived service account key answers “which service is this?” when the real question is “which human asked for this, through which agent, for which task, and for how long?” Traditional identity systems assume the caller is either a person at a keyboard or a deterministic service with a fixed code path. An agent is neither: it acts for a person, chooses its own tool calls at runtime, and reads text that an attacker may have written.

This post lays out a reference design for AI agent identity authorization in 2026. It builds on standards that already exist (OAuth 2.0 Token Exchange, resource indicators, protected resource metadata, SPIFFE) rather than inventing a new identity category, and it is explicit about where the standards are still drafts.

What this covers: why agents break the user-versus-service split, how RFC 8693 delegation and the act claim record who acted for whom, how on-behalf-of flows and the Model Context Protocol (MCP) authorization spec fit together, how workload identity anchors the agent runtime, how step-up approval gates risky actions, and the failure modes (confused deputy, token passthrough) that break all of it.

Context and Background

Identity architecture grew up around two principals. A user authenticates interactively, receives a session or an OAuth access token, and acts through a browser or app. A service (workload) authenticates with a secret, certificate or platform-issued token, and acts with permissions an administrator assigned to it. Authorization models, audit logs and compliance controls all assume you can classify any request as one or the other.

LLM agents blur this. An agent runtime is a service, yet nearly everything it does is on behalf of a particular user, in response to a natural-language instruction the service owner never saw. The set of tools it calls, and the arguments it passes, are chosen by a model at runtime. Worse, the model’s context window mixes trusted instructions with untrusted data (web pages, emails, tool outputs), which is the structural reason prompt injection works. For the attack side of that story see our analysis of agentic AI security and prompt injection.

Teams typically respond in one of three bad ways. They give the agent a powerful service account (so every user’s request runs with the union of all users’ rights). They forward the user’s own token to every downstream tool (so a compromised or confused tool replays it anywhere that user can go). Or they build per-tool API keys pasted into configuration, which are neither user-scoped nor revocable per task. All three lose the delegation chain: afterwards, the audit log says “service-account-17 deleted the table” and nobody can say which human, prompt or agent version was responsible. We examine what that costs in practice in AI agent audit logging and identity lessons.

The good news is that the delegation problem is old, and the IETF standardized its vocabulary years before LLMs. RFC 8693, OAuth 2.0 Token Exchange (January 2020) lets a client ask an authorization server, acting as a Security Token Service, for a new token in exchange for one it already holds, with explicit support for delegation where the actor keeps its own identity. The MCP project then adopted OAuth 2.1 and several companion RFCs as the transport-level authorization for tool servers; the MCP authorization specification defines the roles, discovery, resource-parameter and audience rules we rely on below. For the protocol fundamentals, start with our MCP architecture overview.

The thesis of this post is narrow and practical: an agent should never hold a credential that is either broader than the current task or unattributable to a human. Everything below is machinery for enforcing that sentence.

Why the old assumptions fail

Three properties of agents break identity assumptions specifically:

  1. Non-determinism of the call graph. A service’s outbound calls can be enumerated from its code and allow-listed. An agent’s cannot; the tool list may be dynamic (MCP servers are added at runtime) and the arguments are model-generated.
  2. Confused-deputy exposure by construction. The agent holds authority from the user and also ingests content from third parties. Any instruction in that content can steer the authority. The classic confused deputy problem, named in 1988, is now the default threat model rather than an edge case.
  3. Long, branching delegation chains. A user talks to an assistant, which delegates to a sub-agent, which calls an MCP server, which calls a SaaS API. Each hop is a place where “on behalf of” can be lost or silently widened.

The Reference Architecture for AI Agent Identity Authorization

Direct answer. Give every agent runtime its own cryptographically verifiable workload identity, authenticate the human through normal OAuth, and exchange the user’s token at a token service into a short-lived, audience-restricted token whose claims name both the user (sub) and the agent (act). Tools validate audience and scope, and sensitive actions trigger step-up approval.

AI agent identity authorization reference architecture showing user, agent runtime, token exchange and tool APIs

Figure 1: Where identity breaks in an agent system. Every arrow is a place a credential is minted, forwarded or lost, and the dotted arrow is the untrusted channel from model output back into the agent.

The figure shows why this is harder than classic microservices. The model feeds text back into the agent runtime, and that text can influence which tool is called and with what arguments. So the identity system cannot rely on the agent to police itself; the controls have to sit at the token service and at each resource server, outside the model’s influence.

Three distinct identities, not one

A well-formed design separates three principals that most early agent deployments collapse:

  • The subject is the human (or, for batch jobs, the business process) on whose behalf the work happens. Authority originates here: an agent should never be able to do something its subject cannot.
  • The actor is the specific agent instance, with its own identity, version and owner. This is the “who exactly did it” record. It is what makes revoking one misbehaving agent possible without disabling the user.
  • The workload is the compute substrate (pod, VM, function) that the actor runs on. Its identity is established by platform attestation, not by a secret in an environment variable.

RFC 8693 maps neatly onto the first two. The subject_token represents the party the request is made on behalf of, and the optional actor_token represents the party that will act with the issued token. The RFC distinguishes impersonation, where the actor becomes indistinguishable from the subject, from delegation, where the actor keeps its own identity and the resulting token identifies both. For agents you almost always want delegation: impersonation destroys exactly the attribution you are trying to preserve.

The delegated token

In a delegated token the human appears as sub and the current actor appears in the act claim defined by RFC 8693. A nested act records a chain, with the most recent actor outermost. The RFC is explicit that only the top-level claims and the current actor matter for access control decisions; prior actors in the chain are informational. A sketch of such a claim set (illustrative values, not a real issuer):

{
  "iss": "https://sts.example.com",
  "sub": "user-4821",
  "aud": "https://mcp.example.com/tickets",
  "scope": "tickets:read tickets:comment",
  "act": {
    "sub": "spiffe://example.com/ns/agents/sa/support-agent",
    "act": { "sub": "spiffe://example.com/ns/agents/sa/planner" }
  },
  "iat": 1762650000,
  "exp": 1762650300,
  "jti": "b1c7f0e2-9d8a-4a41-8d5a-0c4a5b2d1e11"
}

Several design choices are visible. The audience is a single resource server. The scope is the minimum for this task. The lifetime is five minutes (an operational choice, not a standard). The actor is identified by a stable workload name rather than an opaque client ID, which makes audit queries (“show everything support-agent did this week”) trivial. And jti allows a resource server or gateway to detect replays where it chooses to track them.

Why audience restriction is the load-bearing control

If you remember one rule, make it this: every token is minted for exactly one resource server, and that server must reject tokens minted for anyone else. RFC 8707 resource indicators let a client say which resource it wants a token for, and RFC 8693 gives the audience and resource parameters for the same purpose at the exchange endpoint. The MCP specification makes this normative: clients MUST send the resource parameter in both authorization and token requests, and servers MUST validate that tokens were issued specifically for them.

Audience restriction turns a stolen token from “access to everything the user can reach” into “access to one tool for a few minutes”. It also kills the most common agent-era design error, token passthrough, which we return to in the failure-modes section.

From User Token to Agent Token: Exchange and On-Behalf-Of Flows

The sequence below is the core runtime flow. The user authenticates once with the client application; the agent service receives a token whose audience is the agent itself; and every downstream call uses a freshly exchanged token minted for that one tool.

On-behalf-of flow for an LLM agent: user token exchanged for a delegated tool token with sub and act claims

Figure 2: The on-behalf-of sequence. The user token never leaves the agent service; each tool receives only a narrow token with sub set to the user and act set to the agent.

Notice what does not happen: the user’s original access token is never forwarded to the tool. The agent service is a resource server for the first token and an OAuth client for the second. That separation is the whole security argument for on-behalf-of designs, and it is the same rule the MCP spec states for MCP servers: if the server calls upstream APIs, “the access token used at the upstream API is a separate token, issued by the upstream authorization server”, and the MCP server MUST NOT pass through the token it received.

The RFC 8693 request, concretely

A token exchange is a POST to the token endpoint with the grant type urn:ietf:params:oauth:grant-type:token-exchange. The required parameters are subject_token and subject_token_type; optional ones include actor_token and actor_token_type, requested_token_type, audience, resource and scope. A delegation request from the agent service looks like this (values are placeholders):

POST /oauth2/token HTTP/1.1
Host: sts.example.com
Content-Type: application/x-www-form-urlencoded
Authorization: Basic <agent client credentials>

grant_type=urn:ietf:params:oauth:grant-type:token-exchange
&subject_token=<user access token with aud=agent>
&subject_token_type=urn:ietf:params:oauth:token-type:access_token
&actor_token=<JWT-SVID or client assertion for the agent>
&actor_token_type=urn:ietf:params:oauth:token-type:jwt
&resource=https://mcp.example.com/tickets
&scope=tickets:read tickets:comment
&requested_token_type=urn:ietf:params:oauth:token-type:access_token

A successful response carries access_token (the field name is historical; the RFC notes the issued token need not be an OAuth access token), a required issued_token_type, a required token_type such as Bearer, and a recommended expires_in. If the granted scope differs from what was requested, scope MUST be present, so the agent can detect silent narrowing. Failures return invalid_request or invalid_target; the latter is the useful one when a policy says “this agent may not obtain tokens for that resource”.

What does the token service check before minting? A policy-rich STS evaluates at least: that the subject token is valid and unexpired; that the actor is a registered agent whose workload identity verifies; that the subject is allowed to delegate to this actor (RFC 8693 describes a may_act claim for exactly this authorization check); that the requested resource is on the allow-list for this agent type; and that requested scope is a subset of what the subject holds. Any one of these failing should refuse the exchange. This is where central policy lives, and it is why the STS, not the agent, must be the place scope narrowing happens.

What the vendors actually ship

Standards are one thing; the identity providers’ implementations are another, and they are moving quickly. Three data points, each with the caveat that vendor documentation changes frequently.

Microsoft Entra Agent ID. Microsoft’s documentation describes an object model of an agent identity blueprint (which owns the redirect URI, API scope and credentials) and agent identities created from it. For an interactive agent acting for a signed-in user, the documented flow is: consent to the agent identity (via inherited permissions, user consent or admin consent), a normal authorization code grant by the client app yielding a token whose audience is the blueprint, then an on-behalf-of exchange for downstream APIs. The docs describe two exchanges under the hood: the blueprint uses its credential to obtain an exchange token, and the agent identity then exchanges that token plus the user’s token for the downstream token. The same docs recommend federated identity credentials or certificates over client secrets for blueprints in production. Read the Entra interactive agent flow documentation for the raw protocol details, which I have not reproduced here.

Enterprise-managed authorization (ID-JAG). A different problem is cross-application: the user is signed in to the company IdP, and an agent in app A needs a token for app B’s API without the user clicking through a consent screen at B. The IETF OAuth working group’s Identity Assertion JWT Authorization Grant draft (draft-ietf-oauth-identity-assertion-authz-grant, version 03 dated 22 April 2026 when I fetched it, authors from Okta, Ping Identity and independent) builds this on RFC 8693 and RFC 7523. The client exchanges the user’s ID token at the enterprise IdP, with requested_token_type set to the ID-JAG type and audience set to the resource authorization server’s issuer. The IdP returns a signed JWT whose header typ MUST be oauth-id-jag+jwt. The client then presents that JWT to the resource authorization server using the jwt-bearer grant (RFC 7523) and gets an access token, with the resource AS applying its own policy and allowed to narrow scope. It is an Internet-Draft, valid at most six months and “may change at any time”, so treat it as work in progress. Vendors including Okta (which markets it as Cross App Access), Keycloak and Atlassian have published material on it; verify current status against their docs before building on it.

MCP authorization. The MCP authorization specification (I read the draft page; the current released revision per the versioning page is 2026-07-28, so confirm clause numbers against the revision you target) defines the MCP server as an OAuth 2.1 resource server and the MCP client as an OAuth 2.1 client. MCP servers MUST implement OAuth 2.0 Protected Resource Metadata (RFC 9728), and clients MUST use it to discover the authorization server. Clients MUST use PKCE with S256, MUST send the RFC 8707 resource parameter, and SHOULD support OAuth Client ID Metadata Documents; Dynamic Client Registration is described as deprecated and retained for backward compatibility. Authorization itself is OPTIONAL for MCP implementations, and stdio transports are told to take credentials from the environment instead. Our MCP 2026-07-28 migration guide covers what else changed in that revision.

Authorization-code login versus token exchange: which hop uses which

A frequent point of confusion is that MCP’s flow and RFC 8693 solve adjacent but different hops. The MCP flow gets a token to the MCP server for a user’s session: the interactive login happens between the MCP client (host application) and the authorization server, with discovery driven by a 401 and WWW-Authenticate header carrying a resource_metadata URL. Token exchange solves server-to-server: a service holding one token needs another for a different audience.

Hop Mechanism Subject Token audience Standard
User to agent app Authorization code + PKCE Human Agent service OAuth 2.1
Agent client to MCP server Authorization code + PKCE with resource Human MCP server MCP auth spec, RFC 8707, RFC 9728
Agent service to internal API Token exchange (delegation) Human, actor = agent Internal API RFC 8693
App A to App B, enterprise managed ID-JAG then jwt-bearer Human App B API Internet-Draft, RFC 7523
Agent runtime to IdP Workload credential Agent IdP SPIFFE, RFC 7523 style assertions

Real systems mix these. A coding assistant might log in to a remote MCP server with the MCP flow (user present), while a scheduled back-office agent uses a client-credentials style grant with its workload identity and an explicit, human-approved mandate. The design question for every hop is the same: whose authority is this, and where is it recorded?

Token formats and validation

Access tokens for resource servers are commonly JWTs, and RFC 9068 defines a profile for JWT access tokens (including the at+jwt type header and the aud, client_id, scope claims). An agent-aware resource server should validate the signature against the issuer’s keys, the iss, the exp, the exact aud, and the scope for the specific operation, and then log sub and act. A tool that validates only the signature has a bearer-token system that any compromised neighbor can abuse. For a catalog of validation pitfalls (algorithm confusion, missing audience checks, clock skew) see our OAuth 2.1, PKCE and JWT validation guide.

Where a bearer token is too weak, bind it to a key. Demonstrating Proof of Possession (DPoP, RFC 9449) or mutual TLS (RFC 8705) make a stolen token useless without the private key. The ID-JAG draft even allows binding the assertion to a DPoP key via a cnf claim. Financial-grade profiles already require these bindings; our FAPI 2.0 article shows the pattern, which transfers directly to high-value agent tools.

Anchoring the Agent: Workload Identity and Short-Lived Credentials

Everything above assumes the token service can tell which agent is asking. If the agent proves itself with a static client secret in an environment variable, you have rebuilt the long-lived key problem one level down. Workload identity replaces the secret with something the platform attests.

Agent workload identity with SPIFFE attestation, IdP federation, token exchange and audit logging

Figure 3: Workload identity chain. Platform attestation yields an SVID, the IdP trusts it, the exchange adds the user, and the audit log records both.

SPIFFE and SPIRE for agent runtimes

SPIFFE (Secure Production Identity Framework for Everyone) defines a name format, the SPIFFE ID (for example spiffe://example.com/ns/agents/sa/support-agent), and short-lived credentials called SVIDs, available as X.509 certificates or as JWTs. SPIRE is the reference implementation: a node agent attests the workload (using kernel and platform facts such as the Kubernetes service account, pod labels or a cloud instance identity document) and issues an SVID without any pre-shared secret. We cover the architecture in depth in SPIFFE and SPIRE workload identity for zero trust, and the Kubernetes-native alternatives in pod certificates versus SPIFFE/SPIRE versus cert-manager.

For agents this buys three concrete things. First, the identity in the act claim is verifiable: the STS can trust it because it validated an SVID, not because the agent claimed a name. Second, rotation is automatic and the lifetime is minutes to an hour, so a leaked credential ages out quickly. Third, the same identity authenticates mTLS between agent and gateway, so network policy and token policy reference one name.

A common pattern is federation: the cloud IdP is configured to trust the SPIFFE issuer’s JWT-SVIDs (or the Kubernetes service account token issuer) as a client assertion, so the agent can authenticate to the IdP’s token endpoint with a signed JWT instead of a client secret. This is the same RFC 7523 style assertion mechanism used elsewhere, and it is what Microsoft’s documentation points toward when it recommends federated identity credentials over client secrets for blueprints. Whichever vendor you use, the principle is the same: the secret the agent presents should be minted by the platform, scoped to that workload, and short-lived.

One identity per agent, not one per deployment

A subtle but important choice is granularity. If all agents in a cluster share one service account, the act claim says “the agent platform”, which is only marginally better than a shared service account. Prefer an identity per agent type and environment at minimum (support-agent in production, not “agents”), and consider per-version identities if you need to revoke a specific release after a vulnerability. Per-run identities are possible but usually overkill; the per-run attribution belongs in the token’s jti and the audit log’s trace ID instead.

Configuration sketch: policy at the token service

The exact syntax depends on your STS, but the policy shape is stable. This illustrative rule set (not tied to a specific product) expresses what each agent type may request:

agents:
  support-agent:
    workload_id: spiffe://example.com/ns/agents/sa/support-agent
    may_exchange_for:
      - resource: https://mcp.example.com/tickets
        max_scope: [tickets:read, tickets:comment]
        max_ttl_seconds: 300
      - resource: https://mcp.example.com/kb
        max_scope: [kb:read]
        max_ttl_seconds: 600
    require_subject_claims:
      acr_min: urn:example:loa:2
    deny_if_delegation_depth_gt: 2
    step_up_required_for: [tickets:delete, tickets:export]

Three guardrails are worth highlighting. max_scope and max_ttl_seconds make the agent’s ceiling explicit and reviewable in code review. deny_if_delegation_depth_gt bounds the nested act chain so sub-agent recursion cannot silently widen authority. And step_up_required_for links this configuration to the approval flow described next.

Capacity and cost reality check

Token exchange adds a network call per tool invocation, which matters because agents may make dozens of calls per task. Mitigate with caching keyed on (subject, actor, resource, scope) for the token’s remaining lifetime, minus a safety margin, and never cache across subjects. A 300-second token used for a task that makes 40 tool calls in 60 seconds needs one exchange rather than 40. The numbers here are illustrative arithmetic, not a benchmark: measure your own STS latency, and put the STS behind the same availability engineering as your IdP, because an agent fleet that cannot mint tokens simply stops. Rate-limit exchange per subject to blunt runaway loops; our distributed rate limiting guide covers the algorithms.

Step-Up Authorization and Human Approval

Scoped, short-lived tokens limit blast radius, but some actions should require a fresh decision even inside a valid session: deleting data, moving money, sending external email, changing permissions. The design question is where the decision is taken and how it is bound to the specific action.

Step-up authorization decision flow for agent actions with scope challenge, human approval and audit

Figure 4: Decision flow for agent actions. Insufficient scope produces a challenge; high-risk actions require approval; every path ends in an audit record.

The protocol mechanism: insufficient_scope challenges

The MCP authorization spec defines runtime step-up using standard OAuth bearer error semantics. When a token lacks scope, the server SHOULD respond 403 Forbidden with a WWW-Authenticate: Bearer header carrying error="insufficient_scope", the scope needed, and the resource_metadata URL. It also says servers SHOULD include all scopes required for the current operation in a single challenge rather than revealing them one at a time, and that servers must handle scope hierarchies. Clients acting on behalf of a user SHOULD attempt step-up: compute the union of the previously requested scopes and those in the challenge, re-authorize, and retry, with retry limits so repeated failure is treated as permanent. Clients acting on their own behalf (client-credentials clients) MAY abort instead.

HTTP/1.1 403 Forbidden
WWW-Authenticate: Bearer error="insufficient_scope",
  scope="tickets:delete",
  resource_metadata="https://mcp.example.com/.well-known/oauth-protected-resource"

The elegance is that the resource server decides what is sensitive, expressed as scopes, and the authorization server decides how to obtain consent for them (a fresh browser prompt, an MFA challenge, a push notification to an approver). The agent holds neither decision; it just relays the challenge.

Binding approval to the action, not the session

Naive step-up approves a scope (“allow delete”) and then lets the agent delete anything under it for the token’s lifetime. For high-risk operations that is too coarse, because a prompt-injected agent that has just been granted tickets:delete can delete the wrong tickets. Two refinements help.

Rich Authorization Requests (RFC 9396) let a request carry structured authorization_details, such as the specific object identifier and operation, so the approved token authorizes only that action. The ID-JAG draft also lists authorization_details as an optional claim, which signals the direction of travel. Out-of-band approval (CIBA-style backchannel flows or a push to the user’s device) shows a human-readable description generated by the resource server or policy engine, never by the model. If the approval screen is rendered from text the agent wrote, an injected instruction can write a deceptive description.

The consent text and the approver identity both need to be in the audit record. Otherwise the approval proves only that someone clicked.

Risk tiers that teams actually operate

Classify tool operations into tiers and attach an authorization pattern to each:

Tier Examples Pattern
Read-only, low sensitivity Search docs, read tickets Pre-consented scope, 5-15 minute token
Read sensitive Export customer data Scope plus data-loss policy, rate limit, log at detail level
Reversible write Draft email, create ticket Scope; allow with undo window and audit
Irreversible or external Delete, send external, payments Step-up with action-bound authorization_details and human approval
Privilege change Grant access, rotate keys Never delegated to agents; human-only path

Fixed tiers beat per-request judgment by the model, because they are testable and auditable. Note this is a design pattern, not a standard; calibrate the tiers to your own risk appetite and regulatory context.

Trade-offs, Gotchas, and What Goes Wrong

Token passthrough and the confused deputy

The single most common failure is the agent gateway or MCP server forwarding the inbound token to an upstream API. It is attractive because it works with no extra integration. It is also forbidden: the MCP spec says servers MUST only accept tokens specifically intended for themselves, MUST reject tokens that do not name them in the audience, and MUST NOT pass through the token they received. The consequences are concrete. The upstream API cannot distinguish the agent from the user, the audit trail collapses, and any server in the chain that logs or leaks the token hands out the user’s full reach.

The related confused deputy attack targets MCP proxy servers that front a third-party API with a static client ID. The spec requires such proxies to obtain per-client user consent before forwarding to the third-party authorization server, because an attacker with a stolen authorization code can otherwise obtain tokens without the user’s knowledge. Our MCP server security threat model walks through tool poisoning and these attack paths in detail.

Over-broad exchange policy

Token exchange is only as safe as the STS policy. A permissive policy (“any registered agent may exchange for any resource with any scope the user has”) reproduces the powerful-service-account problem with extra steps. Treat the allow-list of (agent, resource, max scope) as security-critical configuration, version it, and review changes. Watch for scope escalation through scope hierarchies: if a broader scope implies narrower ones, your narrowing logic must handle the implication in both directions.

Delegation-chain sprawl

Nested act claims are informative, but RFC 8693 states that only the top-level claims and the current actor matter for access control. If a sub-agent receives a token whose current actor is the sub-agent, the planner that spawned it is just history. That is correct for least privilege, yet it means long chains can launder responsibility unless your audit pipeline stores the full chain. Cap depth, and require each hop to re-exchange rather than forward.

Refresh tokens and offline access

Agents running unattended tempt teams to store a user’s refresh token. The MCP spec says clients MUST keep refresh tokens confidential, MUST NOT assume they will be issued, and that servers SHOULD NOT advertise offline_access as a resource requirement. A stored refresh token is effectively a long-lived credential for a human, which undoes the short-lived design. For background work prefer a distinct agent identity with an explicit, owner-approved mandate over silently reusing a person’s session.

Other things that go wrong

  • Token caching across subjects causes cross-user leakage; key every cache on the full tuple.
  • Clock skew and expiry mid-task cause flaky failures in long agent runs; handle 401 by re-exchanging, not by retrying blindly.
  • Prompt-supplied tokens: a token that appears in model context can be echoed into logs, tool arguments or outputs. Keep credentials in the runtime, outside the model’s context, and redact them in traces. See our notes on OpenTelemetry for LLM agents for attribute hygiene.
  • Metadata fetch risks: authorization servers that fetch Client ID Metadata Documents must consider server-side request forgery, and localhost redirect URIs cannot be proven by metadata alone, both called out in the MCP security considerations.
  • Draft instability: ID-JAG and the OAuth 2.1 draft are not final RFCs. Isolate them behind your own interface so a wire change is a patch, not a rewrite.

What identity cannot fix

Authorization limits what an injected agent can do; it does not stop injection. An agent holding a legitimate, narrow tickets:comment token can still be steered to post a harmful comment. Identity controls bound the damage and make it attributable, which is necessary but not sufficient. Pair them with input isolation, output filtering and sandboxing; see our comparison of agent code-execution sandboxes.

Practical Recommendations

Start from the data flow, not the vendor. Draw every hop from human to data, mark which credential is used at each, and eliminate any hop that forwards a user token or uses a shared secret. Then move in this order, because each step makes the next safer.

First, give agents their own identity and stop using human or shared service credentials. Prefer platform-attested workload identity (SPIFFE, Kubernetes projected tokens, cloud managed identity) federated to your IdP. Second, mint tokens per audience with RFC 8693 delegation or your IdP’s on-behalf-of equivalent, so claims carry the user as sub and the agent as act. Third, enforce audience and scope at every resource server, including internal ones. Fourth, add step-up for the tiers where a mistake is irreversible. Fifth, log the chain: subject, actor, resource, scope, decision, trace ID, approval ID.

A pre-production checklist:

  • [ ] Each agent type has a distinct, attested workload identity; no static client secrets in agent config.
  • [ ] Every outbound tool call uses a token with a single aud, minimal scope and a lifetime of minutes.
  • [ ] Tokens carry both sub (human) and act (agent); resource servers log both.
  • [ ] No token passthrough anywhere; resource servers reject mismatched audiences (test this with a negative test).
  • [ ] STS policy lists allowed (agent, resource, max scope, max TTL) and caps delegation depth.
  • [ ] High-risk operations require step-up with action-bound details and approver identity in the log.
  • [ ] Refresh tokens are not stored for unattended agents; background agents use their own mandates.
  • [ ] Kill switch: you can revoke one agent identity without disabling its users.
  • [ ] Draft-standard dependencies (ID-JAG, OAuth 2.1) are behind an abstraction and tracked for changes.

Finally, test the failure paths deliberately. Replay a token to the wrong tool, request an out-of-policy scope, exceed the delegation depth, and revoke an agent mid-task. If any of these succeeds quietly, the design is leaking.

Frequently Asked Questions

What is AI agent identity authorization?

It is the set of controls that establish who an AI agent is, whose authority it exercises, and what it may do right now. In practice it combines a verifiable identity for the agent runtime, user authentication via OAuth, delegation tokens that name both user and agent, audience and scope enforcement at each tool, and audit records of the full chain. The aim is that no agent holds credentials broader than its current task.

What is the difference between OAuth token exchange and an on-behalf-of flow?

Token exchange (RFC 8693) is the standard: a client presents a subject token, optionally an actor token, and receives a new token for a different audience or scope. “On-behalf-of” is the common name for implementations of the same idea, such as Microsoft’s OBO flow, where a service exchanges an incoming user token for a downstream token that still represents the user. The parameters differ by vendor, so check your IdP’s documentation.

What do the sub and act claims mean in a delegated agent token?

In an RFC 8693 delegated token, sub identifies the party the token represents, typically the human, and act identifies the current actor to whom authority was delegated, typically the agent. Nested act claims record a chain, most recent actor outermost. Only the top-level claims and the current actor are used for access decisions; earlier actors are informational, which is why audit systems should store the entire chain.

Why is token passthrough prohibited in MCP?

Because the upstream API then cannot tell the agent from the user, and every intermediary becomes a place where the user’s full-reach token can leak or be abused. The MCP authorization specification requires servers to accept only tokens issued for them and forbids passing the received token to upstream APIs. The correct pattern is for the server to act as an OAuth client and obtain a separate token for the upstream service.

Do agents need SPIFFE or can I just use API keys?

API keys work until one leaks, and then they are long-lived, hard to attribute and broad. SPIFFE-style workload identity issues short-lived, automatically rotated credentials proven by platform attestation, so the agent’s identity in an act claim is verifiable. It is not the only route: Kubernetes projected service account tokens and cloud managed identities give a similar property. What matters is that no static secret lives in the agent’s configuration.

How should agents handle actions that need human approval?

Have the resource server challenge with insufficient_scope for the missing permission, then route the client through step-up authorization, ideally with out-of-band approval and action-bound details (RFC 9396) so the approval covers only that operation. Render the approval text from trusted policy, not from model output, and log the approver and the exact action approved. Fail closed if approval is denied or times out.

Further Reading

By Riju — about

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *