top of page

Per-Model Identity Is Not Enough: Why AI Workload Identity Needs to Be Per-Agent

peterrivera813
5 days ago
7 min read


There is a moment in almost every enterprise AI project where somebody says, "we're using managed identity, so we're good on auth." I have said it myself. It felt true at the time, because the thing we were securing was a model endpoint. One application, one identity, one set of role assignments, one line in the architecture diagram. Clean.

That model held up fine when the workload was a chat app calling a deployment and returning tokens to a user. It does not hold up now. The workload is no longer one process calling one endpoint. It is a collection of agents, each with its own tools, its own downstream systems, and its own ability to take actions that have consequences in your environment. The identity boundary that made sense for a model deployment is the wrong shape for that.

This post is about why the boundary moved, what Microsoft has actually shipped to address it, and the specific configuration mistakes I keep running into when teams get halfway there.


The boundary moved but most architectures didn't


Think about what a model deployment actually does. It accepts a prompt and returns a completion. It has no hands. The blast radius of a compromised or misbehaving model endpoint is bounded by the data you put in front of it and the data it returns. Assigning one workload identity to the application that calls it is a reasonable, proportionate control.

Now think about what an agent does. It reasons about a goal, decides which tool to invoke, calls that tool, reads the result, and decides what to do next. The tools are the interesting part: a storage account, a Logic App, a Cosmos DB collection, an MCP server sitting in front of a line-of-business API, another agent entirely. The agent has hands. It writes, it triggers, it queries, it delegates.

When you give a fleet of agents one shared identity, you have effectively told your environment that every agent is every other agent. The agent that summarizes public marketing content holds the same token scope as the agent that reconciles invoices. Nothing in your audit log distinguishes them, because from Entra's point of view there is nothing to distinguish. You have built a system where least privilege is arithmetically impossible: the shared identity's permission set has to be the union of every agent's needs, which means every agent operates at the high-water mark of the most privileged one.

For leadership, that is the whole argument in one sentence. If you cannot answer "which agent did this?" from your logs, you cannot answer it in an incident review either, and you cannot scope the response to anything narrower than "all of them."


What Microsoft actually built


The good news is that Microsoft did not ask us to solve this with naming conventions and hope. Microsoft Entra Agent ID is now generally available, and it introduces two constructs that are worth understanding precisely, because the distinction between them is where most implementation errors live.

An agent identity is a specialized service principal in Entra ID that represents a single agent at runtime. It is the principal that shows up in sign-in logs, the principal you assign RBAC roles to, and the principal a downstream resource evaluates when it decides whether to honor a token.

An agent identity blueprint is the governing template that a class of agent identities is created from. It is roughly analogous to a policy definition in the Azure Policy world: you define it once, agents inherit from it, and administrative controls applied at the blueprint level flow down to every identity derived from it, including ones created later. The blueprint also holds the OAuth credentials that the hosting service uses to authenticate and mint tokens for its agent identities.

Those credentials can be a client secret, a certificate, or a federated credential backed by a managed identity. The federated credential option is the one you want in production, and it is what Microsoft Foundry (the platform formerly branded Azure AI Foundry) wires up by default. When Foundry provisions a blueprint for your project, it establishes a federated trust between that blueprint and the project's managed identity. No secret is stored in the blueprint configuration, and Azure handles rotation through the managed identity's own infrastructure.

Here is the part that trips people up, and it is worth reading twice: the managed identity authenticates the blueprint. It does not access the downstream resource. The agent identity is the principal that needs the RBAC role assignment on the storage account, the Cosmos DB account, or the Logic App. If you grant Storage Blob Data Contributor to the project's managed identity and wonder why the agent still gets 403s, that is why.

The runtime flow, in plain terms, goes like this. The Agent Service presents the blueprint's credentials to Entra ID. Entra validates them and issues a token for the specific agent identity. The Agent Service hands that token back to Entra and asks for a new access token scoped to the audience of the downstream service. That scoped token goes to the tool endpoint, which validates it and checks the agent identity's role assignments before allowing anything. Four steps, all handled by the platform, no token management in your application code.


The three mistakes I keep seeing


Mistake one: shipping to production on the shared project identity.


When you create your first agent in a Foundry project, the platform provisions a default blueprint and a default agent identity, and every unpublished agent in that project shares it. This is a deliberate design choice and a sensible one for development, because it keeps developers from filing a permissions ticket every time they spin up an experiment. It is not a production posture. The moment an agent needs its own permission set, its own audit trail, or its own lifecycle, it needs to be published so it gets a distinct blueprint and a distinct agent identity bound to the agent application resource.


Mistake two: assuming role assignments follow the agent when you publish it.


They do not. Publishing creates a new agentIdentityId, and the roles you assigned to the shared project identity do not carry over. This is a genuinely nasty failure mode because it presents as a working agent in dev that breaks on promotion, and the error surface points at the tool rather than at the identity. Build the role reassignment into your deployment pipeline as an explicit step. The Azure Developer CLI gives you some automation here, but it does not configure Container Registry, Application Insights, or custom resource permissions for you, so treat the automated path as a starting point rather than a finish line.


Mistake three: getting the audience wrong and blaming RBAC.


Every scoped token is issued for exactly one audience, and the audience must be the OAuth resource identifier of the target service — https://storage.azure.com for Storage, https://vault.azure.net for Key Vault, https://graph.microsoft.com for Graph. It is not the URL of the MCP server that fronts the resource. An incorrect audience produces an authentication failure that looks exactly like a missing role assignment, and I have watched teams spend an afternoon re-granting permissions that were correct the entire time.


Where Conditional Access does and does not reach


This is the section I would put in front of a security leader, because it is where the theory meets a real gap.


Conditional Access for agents works, but what you target depends on how the agent authenticates. When an agent acts on behalf of a signed-in user through the OBO flow, the user is the subject of the token, so your policies target users and groups — not agent identities. When an agent acts autonomously under its own authority via client credentials, the agent identity is the subject, and that is what your policy scopes to. And if the agent has been given its own user account to function as a digital worker with a mailbox, policy is evaluated against that account. A policy targeting agent identities will not cover an agent's user account, and neither will one targeting the blueprint.


There is also a boundary that matters more than any of the above: Conditional Access only protects resources secured by Entra ID. If an agent reaches a tool using an API key, it bypasses the Entra token pipeline entirely and no policy applies. That is the whole argument for routing agent traffic through a governed gateway and eliminating key-based tool connections wherever the tool supports agent identity authentication. Today that means MCP and agent-to-agent connections; other integrations may still fall back to keys or OAuth passthrough, and you should treat each of those as a documented exception rather than a default.


At scale, targeting individual agent identities in individual policies collapses under its own weight. Custom security attributes are the escape hatch — tag agent identities by environment, sensitivity tier, and sponsoring team, then write policies against the attributes so new agents inherit enforcement on creation rather than on remembering.


What to actually do about it


Start by inventorying what you have. Entra ID gives you a full agent identity inventory across Foundry, Copilot Studio, and other sources, and that list is usually longer and more surprising than people expect. Then work through three questions for each agent: does it have its own identity or is it riding the shared project one, are its role assignments scoped to the specific resource rather than the resource group or subscription, and does it reach any tool through a key rather than a token?

None of this is exotic. It is the same least-privilege discipline we have applied to service principals for a decade, applied to a class of workload that creates and destroys principals faster than any change process was designed for. The difference is that agents act, they act quickly, and they act on behalf of decisions no human reviewed. That is precisely why the identity has to be per-agent — not per-model, and not per-project.

If your architecture diagram still shows one identity feeding a box labeled "AI," you have not modeled the thing you are actually running.

 
 
 

Comments


bottom of page