Winslow Homer watercolour of a harbour under sail, used as the article cover
← Back to blog

PRIMER

Identity for AI Agents: A Technical Primer

The IETF now models an agent as a workload with exactly one identifier. What that means in claims, credentials and audit records, with the spec text for each and the four ways teams get it wrong.

•May 11, 2026•Updated Oct 13, 2026•15 min
IdentityAI AgentsSecurity

TL;DR

An agent is neither a human user nor a service account, and the IETF's working draft on the subject resolves that by calling it a third thing: a workload that acts for someone.

draft-klrc-aiagent-auth-03 requires that an agent "MUST be assigned exactly one WIMSE identifier", and says that when the agent acts for a person, the person's identifier goes in the access token's sub claim while the agent's goes in client_id. Two identifiers on one request, both required.

The usual shortcut is to give the agent a service account and move on. It runs. It also collapses those two identities into one, which is why the question that matters after an incident, which agent did this and on whose authority, has no answer in the logs.

We are not claiming this needs a new protocol. Almost none of it is new. SPIFFE supplies the identifier, OAuth the delegation, RFC 8693 the actor claim, RFC 9728 the discovery. What is new is the requirement that both identifiers survive together all the way to the resource server.

Start by decoding an access token your agent is using right now. If sub and client_id name the same thing, you have one principal where the draft says you need two.

Overview

Enterprise identity has two boxes. A human authenticates and gets a session. A machine runs under a service account whose permissions were sized once, at setup, for one job. Every provisioning flow, entitlement review and revocation path in a mid-sized enterprise assumes a principal is one or the other.

An agent is software that improvises. It picks a different set of tools depending on the request, interprets instructions rather than executing a fixed routine, and chains calls across systems that were never introduced to each other. The IETF draft covering this states the framing in its section 4 heading: agents are workloads. An agent "iteratively interacts with a Large Language Model (LLM) and a set of Tools, Services and Resources" until a terminating condition is reached, and it "may receive input from a user, or act autonomously."

That last clause is where the old model runs out. A workload with no user behind it is a service account, and the existing machinery handles it fine. A workload acting for a specific person is something the existing machinery has no shape for, because the person's authority and the agent's have to travel together and be evaluated together at the far end.

This primer covers the mechanics: which identifier the agent gets, which credential proves it, which claims carry the user alongside it, and what a resource server is meant to do with both. The protocol-specific gap in MCP is covered separately in the MCP OAuth permissioning problem, and the distinction between proving identity and bounding an action in agent authorization vs. authentication.

The core requirement: an agent has to be attributable and revocable on its own, not through the identity of whoever deployed it. Everything below is one piece of making that true on the wire.

Why the Two-Principal Model Breaks

A service account was designed for software that does the same thing forever. Its permissions were sized once for that job, and the sizing stayed correct because the job did. An agent has no single job. The tools it touches are chosen at runtime by a model reading a request, so the permission set has to cover the union of everything it might be asked to do. That union is always wider than any one task needs.

Put a variable workload behind a fixed grant and the grant becomes the blast radius. Nothing about that is speculative, and the platform vendors have started acting on it. Microsoft Entra Agent ID gives agents their own resource type in the directory rather than reusing the application object, and it hard-blocks a list of high-privilege Microsoft Graph permissions for agent identities specifically, "because of the autonomous nature of agents and the potential risks they pose." Ask for one anyway and the request is rejected with an HTTP 400 Bad Request saying the permission cannot be granted to agent identities. Directory.ReadWrite.All, Files.ReadWrite.All, RoleManagement.ReadWrite.Directory and User-PasswordProfile.ReadWrite.All are on that list. A human admin can still hold every one of them.

That is a vendor drawing a line the standards had not: the same permission is fine for one class of principal and not for another, which only works if the two classes are distinguishable at grant time. They are not distinguishable when the agent runs under a shared service account.

The second failure is quieter. Without a distinct identity, every action is attributed to whatever account the agent borrowed. The log records that the account acted. It cannot record which agent, on whose behalf, executing which instruction, because none of that was carried on the request. We think that is why agent incidents read as confusing afterwards. Not that nothing was logged. That what was logged points at a principal which did not make the decision.

The Agent as a First-Class Principal

There are four ways teams model this, and the choice determines what you can answer later.

ModelWhat the resource server seesAttributionHow you cut it offStanding blast radius
Inherits the user's session, aact:identity-model/shared-human-identityThe user, and only the userNone for the agentKill the user's session, which also stops the humanEverything that user can do
Shared service account, aact:identity-model/shared-service-accountOne machine principalWhich agent? UnanswerableRotate the secret, breaking every agent that shares itUnion of every agent's needs
Per-agent service account, aact:identity-model/per-agent-identityThat specific agentAgent yes, acting user noDisable that principalWhatever it was granted, permanently
Workload identity plus a delegated grant, aact:identity-model/workload-identityAgent and acting user, separatelyBoth, on every callDisable the agent identity, or revoke the grantIntersection of the two

Table 1 — Four ways to model an agent principal, and what each one costs you at incident time.

Rows one and four are the interesting pair, because RFC 8693 already named the difference. Under impersonation, "A is given all the rights that B has within some defined rights context and is indistinguishable from B in that context." Under delegation, "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."

Indistinguishable is the operative word. Row one is not weak attribution. It is the deliberate absence of it, which a spec offers because some cases want it. Agents are not one of those cases.

Row four also fixes the sizing problem, because the agent's authority stops being a standing grant and becomes an intersection. Microsoft states this for delegated Graph permissions in the plainest available terms: "the application can't access anything the signed-in user couldn't access," and the privileges an app has acting for a user are determined by the permissions the app was granted and the user's own. Two constraints, both applied, neither sufficient alone.

Identifiers and Credentials

An identity starts with a stable name. The draft is unambiguous about this: "Agents MUST be uniquely identified in order to support authentication, authorization, auditing, and delegation," and the identifier it standardises on is the WIMSE identifier, "a URI that uniquely identifies a workload within a trust domain." Authorization decisions, delegation semantics and audit records "rely on this identifier remaining stable for the lifetime of the workload identity." An agent "MUST be assigned exactly one."

SPIFFE is the deployed instance of that model, which the draft calls "widely deployed and operationally mature." A SPIFFE ID is a URI of the form spiffe://<trust-domain>/<path>, and the credential that proves it is an SVID, "the document with which a workload proves its identity to a resource or caller," issued either as an X.509 certificate or as a JWT.

A name proves nothing on its own, so section 7 pairs it with a credential requirement: agents "MUST possess credentials that provide a cryptographic binding to the agent identifier." A shared API key in an environment variable does not provide that binding. Any process that can read the variable can present it.

Together on one request, the SPIFFE ID names the workload and the access token names both the workload and the person it acts for:

json
{
  "iss":  "https://as.example.com",
  "aud":  "https://mcp.example.com",
  "sub":  "user:a.mensah@example.com",
  "client_id": "spiffe://example.com/agents/ap-exception-bot",
  "scope": "invoices:read invoices:annotate",
  "exp":  1778112000,
  "jti":  "c0ffee-7b31-4f2a-9d55-8ab41e2c77de"
}

sub is the person. client_id is the agent. Both are required fields under RFC 9068, which defines the JWT profile for access tokens, and the draft leans on exactly that structure.

Carrying Two Identities Through Delegation

RFC 9068 section 2.2 sets the rule the draft then applies. For tokens obtained through a grant where a resource owner is involved, "the value of sub SHOULD correspond to the subject identifier of the resource owner." For grants with no resource owner, such as client credentials, sub "SHOULD correspond to an identifier the authorization server uses to indicate the client application." client_id is required either way.

The two shapes are therefore distinguishable by inspection. An agent acting for a person carries a human in sub and a workload in client_id. An agent acting for itself carries the same value in both. It is the cheapest identity audit available.

Delegation across hops needs a different mechanism, because sub has one slot. RFC 8693 supplies it. The act claim "provides a means within a JWT to express that delegation has occurred and identify the acting party to whom authority has been delegated," and chains nest: "The outermost act claim represents the current actor while nested act claims represent prior actors." There is a matching gate. The may_act claim "makes a statement that one party is authorized to become the actor and act on behalf of another party," so the authorization server can refuse a delegation it never sanctioned rather than minting whatever is asked for.

Resource serverWorker agentPlanner agentUserResource serverWorker agentPlanner agentUseract = worker, nested act = plannerrequest (user authenticates)token: sub=user, client_id=plannersubtask + exchanged tokencall with narrowed scopevalidate aud, read sub + act chainresult, logged against both identities

Figure 1 — A two-hop delegation that keeps the user in sub and records each actor in a nested act claim.

The draft adds a refinement worth knowing about for anything that fans out internally. Access tokens are usually good for many transactions, so passing one between microservices "increases the risk of token theft and replay attacks." Its answer is the transaction token: a downscoped token "bound to a specific transaction" that "cannot be used as an access token, with another transaction, or within the same transaction with modified transaction details." Change the amount on a payment mid-chain and the token no longer applies.

A delegation chain that can widen scope at any hop has already defeated the reason scoping existed. Narrowing is the invariant. may_act is how the authorization server enforces it rather than trusting each hop to behave.

MCP and OAuth

MCP standardises how an agent discovers and calls tools. As of the 2026-07-28 revision it also takes a firm position on identity, by declaring that "a protected MCP server acts as an OAuth 2.1 resource server" and inheriting the obligations that come with that role.

Three of those obligations do real work. MCP servers "MUST implement OAuth 2.0 Protected Resource Metadata (RFC9728)" so a client can discover which authorization server to talk to, and must answer an unauthenticated call with a 401 carrying a WWW-Authenticate header pointing at that metadata. Clients "MUST implement Resource Indicators for OAuth 2.0 as defined in RFC 8707," sending a resource parameter that names the specific server the token is for. And servers "MUST validate that access tokens were issued specifically for them as the intended audience."

The third is the one that keeps an agent's credentials from becoming a skeleton key. If the MCP server calls an upstream API, the spec is explicit that this is a separate token from a separate authorization server, and that the "MCP server MUST NOT pass through the token it received from the MCP client." Forwarding it unchanged is the confused deputy shape, where the downstream API trusts a token it should never have seen.

MCP also separates the two failure modes cleanly, which is more than most homegrown gateways manage. A 401 means authorization is required or the token is invalid. A 403 means the identity is fine and the scopes are not, and the server should return error="insufficient_scope" naming the scopes the operation needs, so the client can step up instead of guessing.

Audit and Revocation

Identity that leaves no record is unverifiable, which for practical purposes means it is not identity. The draft treats this as normative rather than operational: deployments "MUST maintain sufficient monitoring and observability to reconstruct agent behavior and authorization context after execution," and "observability is therefore a security control, not solely an operational feature."

It then lists what an audit event must carry, at minimum:

  • the authenticated agent identifier
  • the delegated subject, user or system, when present
  • the resource or tool being accessed
  • the action requested and the authorization decision
  • a timestamp and a transaction or request correlation identifier
  • the posture assessment or risk state that influenced the decision
  • remediation or revocation events and their cause

Records "MUST be tamper-evident." We think most agent logging already carries the third and fifth items and stops there, which is enough to reconstruct a sequence and not enough to reconstruct authority.

Revocation is where a token's face value stops being good enough. NIST's zero trust tenets already set the bar: access is "granted on a per-session basis," and "all resource authentication and authorization are dynamic and strictly enforced before access is allowed." The agent draft turns that into a hard rule for cached state. On receiving a signal such as session revoked, subject disabled or token replay suspected, recipients "MUST ensure that revoked or downgraded authorization is enforced without undue delay," and "cached authorization decisions and tokens that are no longer valid MUST NOT continue to be used." The transport it suggests is the OpenID Shared Signals Framework with CAEP or RISC profiles, which is an existing eventing standard rather than something anyone has to invent.

Reference Architecture

Put the pieces together and an agent identity layer has five parts.

Upstream IdP
human identity

Agent registry
one stable identifier per agent

Permissions
action-level, user ∩ agent

Token broker
short-lived, audience-bound

MCP server / tool

Audit pipeline
tamper-evident record

Agent registry (one stable identifier per agent) --> Permissions layer (action-level; user scopes intersected with agent scopes) --> Token broker (short-lived, audience-bound token) --> MCP server / tool Token broker also writes to --> Audit pipeline (tamper-evident record) -->

Figure 2 — A request's path through the identity layer. Every hop narrows what the next one can do, and the audit pipeline records the path whether or not the call succeeds.

The upstream IdP integration inherits human identity rather than reinventing it. The registry holds one stable identifier per agent with a real lifecycle, and Entra Agent ID is a sanity check on what that lifecycle needs: owner and sponsor metadata so somebody is accountable, access reviews to "verify that agent identities still need the access they have", and lifecycle workflows so sponsorship transfers instead of evaporating when a person leaves. The permissions layer evaluates on every call, not once at setup. The broker mints short-lived, audience-bound tokens. The audit pipeline writes the seven fields above somewhere queryable.

The failure mode is treating any one of these as sufficient. A registry with no permissions layer tells you the agent exists. A permissions layer with no broker has nowhere to enforce what it decided. A broker with no audit pipeline makes correct decisions nobody can reconstruct three weeks later.

Where Fabriq Fits

Agentic Fabriq holds credentials per user in a vault and attaches them server-side at the moment of the call, so the agent never handles the secret. Every request carries two identities, the agent and the acting user, and an agent's effective tool list is the intersection of the user's scopes and the agent's, which is row four of Table 1 rather than row three. Refresh runs behind that boundary with last-used tracking, and calls land in an audit record.

What it does not do is reach into the provider on your behalf. Clearing a vault copy of a credential is not the same act as invalidating a grant upstream, so the call to the provider's revocation endpoint remains yours to make.

Frequently Asked Questions

Does an AI agent need its own identity, or can it use the user's? It needs its own. RFC 8693 calls reusing the user's identity impersonation, and defines it as the acting party being "indistinguishable from B in that context." Indistinguishable means your audit trail cannot separate the agent's actions from the person's, which is the one thing you will want after an incident.

What is a WIMSE identifier? A URI that uniquely identifies a workload within a trust domain, defined by the IETF's Workload Identity in Multi-System Environments work. draft-klrc-aiagent-auth-03 makes it the primary identifier for agents and requires exactly one per agent. A SPIFFE ID, of the form spiffe://<trust-domain>/<path>, is a widely deployed instance of the same model.

Is SPIFFE the right identity for an AI agent? For the workload half, yes. SPIFFE names the process and the SVID proves it cryptographically. What it does not carry is the user the agent acts for, so you still need an OAuth grant alongside it.

How do I know if my agent has two identities or one? Decode its access token. Under RFC 9068, sub should hold the resource owner for user-delegated grants and the client for client-credentials grants, and client_id holds the agent. If both fields name the same principal, the agent is acting purely for itself regardless of which human triggered it.

What is the act claim for? Expressing delegation inside a JWT. RFC 8693 defines it as identifying "the acting party to whom authority has been delegated," and lets chains nest, with the outermost act being the current actor and inner ones the prior actors. may_act is the companion claim stating who is permitted to become an actor for whom.

Can I just revoke the token when an agent misbehaves? Only if something rechecks. A bearer token stays valid for its full lifetime unless the resource server consults current state, which is why the draft requires that cached authorization decisions and invalid tokens "MUST NOT continue to be used" after a revocation signal, and points at Shared Signals with CAEP as the delivery mechanism.

Do I need a new standard for any of this? No. The draft's own abstract says it "leverages existing standards" rather than proposing new protocols: SPIFFE or WIMSE for the identifier, OAuth 2.1 for the grant, RFC 9068 for the token shape, RFC 8693 for delegation, RFC 9728 for discovery. The assembly is the work.

What must an agent audit record contain? Seven fields, per section 11 of the draft: agent identifier, delegated subject, resource or tool, action and decision, timestamp plus correlation ID, posture or risk state, and any remediation event with its cause. Records must be tamper-evident.

Conclusion

The primitives were never the problem. OAuth works. An identity provider works. SPIFFE has been issuing workload identities in production for years. What was missing was a principal that acts on someone else's behalf, chains across tools that do not know about each other, and needs its authority checked on every call rather than once at login.

That gap is closing in public: in an IETF draft, and in directory products that now ship a distinct resource type for agents. Both arrived at the same answer. One stable identifier per agent, a credential cryptographically bound to it, the acting user preserved alongside it in the token, and an audit record carrying both.

Build the token check first. Decode what your agents present, look at sub and client_id, and see whether the two identities the spec requires are actually there.

If you cannot say which agent took a given action, on whose authority, and how to stop it on the next request, the identity layer is not built yet. Two claims and a tamper-evident log get you most of the way.

Sources

All URLs read 2026-09-29.