Giving your Teams bot an identity with Microsoft Entra Agent ID

If you’ve been reading this blog over the years, you’ve watched me wrestle with bot identity in real time. Back in 2017 I built the SPAdminBot with a client ID, a client secret, and a lot of trust that nobody would accidentally commit those credentials to GitHub. Then in 2020 I wrote about stopping the use of client secrets and moving to Managed Identities. That was a genuine improvement. But I always had this nagging feeling that we were bolting identity onto bots as an afterthought, shoehorning agents into an authentication model designed for web apps and background services. At Ignite in November 2025, Microsoft announced Entra Agent ID, and for the first time it feels like agents are getting identity that was actually designed for them.
The identity problem I’ve lived
Here’s the thing nobody talks about at conferences. When you register an app in Azure and give it Sites.FullControl.All because your bot needs to read one list on one site, you know it’s wrong. I know it’s wrong. We all know it’s wrong. But the permission model doesn’t let you say “just this site, just read access.” So you grant the nuclear option and move on, hoping your bot never gets compromised. I’ve done this more times than I’d like to admit.
Then there’s secrets. I wrote an entire blog post telling you to stop using them, and I meant every word. But even with Managed Identities, you still end up with broad application permissions that don’t distinguish between contexts. Your bot running a scheduled job at 3 AM has the same identity and the same permissions as your bot responding to a user in a Teams conversation. There’s no way for the system to know the difference, and there’s no way to scope things down to what the bot actually needs in that specific moment.
What is Entra Agent ID
Entra Agent ID introduces something called Agent Identity Blueprints. Think of a blueprint as a declarative definition of what your agent is allowed to do. Instead of granting broad permissions at registration time and hoping for the best, you define the boundaries up front and the agent gets just-in-time scoped tokens at runtime. The agent asks for exactly what it needs, for exactly the resource it needs, in exactly the context it’s operating in. If that sounds like Zero Trust, that’s because it is. It’s Zero Trust applied to bots, and honestly it’s about time.
The old way
Let’s look at what we’ve been doing. This should feel painfully familiar if you’ve built any Teams bot that talks to the Graph.
var confidentialClient = ConfidentialClientApplicationBuilder
.Create("your-app-client-id")
.WithClientSecret("your-long-lived-secret") // The thing post 053 told you to stop using
.WithAuthority($"https://login.microsoftonline.com/your-tenant-id")
.Build();
// This token has Sites.FullControl.All, way more than the bot needs
var authResult = await confidentialClient
.AcquireTokenForClient(new[] { "https://graph.microsoft.com/.default" })
.ExecuteAsync();
That .default scope is doing a lot of heavy lifting there. It means “give me everything this app registration is allowed to do.” And because you granted Sites.FullControl.All just to read one site, your token can now do anything to any site in the tenant. Every single request carries the same broad permissions regardless of what the bot is actually trying to do. Not great.
The new way
Now let’s look at the Entra Agent ID approach. First off, notice how different the intent is.
using Microsoft.Identity.AgentPlatform;
var agentIdentity = new AgentIdentityClient(
agentBlueprintId: "your-agent-blueprint-id",
tenantId: "your-tenant-id"
);
var scopedToken = await agentIdentity.AcquireTokenAsync(new AgentTokenRequest
{
RequestedScopes = new[] { "Sites.Read.All" },
ResourceConstraints = new ResourceConstraint
{
ResourceUri = "https://contoso.sharepoint.com/sites/ProjectAlpha",
Operations = new[] { "read" }
},
ConversationContext = new ConversationContext
{
ConversationId = turnContext.Activity.Conversation.Id,
UserId = turnContext.Activity.From.AadObjectId
}
});
var graphClient = new GraphServiceClient(
new DelegatedTokenCredential(scopedToken.AccessToken));
var site = await graphClient
.Sites["contoso.sharepoint.com:/sites/ProjectAlpha:"]
.GetAsync();
Look at what’s happening here. The agent requests Sites.Read.All (read, not full control). It constrains the resource to a single site. And it passes along the conversation context so the identity system knows who the bot is talking to and why it needs access. The token that comes back is scoped to exactly that operation. If someone intercepted it, they couldn’t use it to delete a different site or read someone’s mailbox. This way, the blast radius of a compromised token shrinks from “the entire tenant” to “read access on one SharePoint site.”
No secrets are stored anywhere. No certificate rotation. No client secrets expiring on a Friday afternoon. The blueprint defines the boundaries and the runtime enforces them.
Zero Trust for bots
Here’s what gets me genuinely excited. IT admins can now apply Conditional Access policies to agent identities. Think about that for a second. The same framework that enterprises use to control user access (require MFA, block risky sign-ins, restrict by location) can now be applied to agents. Your security team can say “this agent can only access resources during business hours” or “this agent can’t access sensitive sites unless the requesting user has a specific security clearance.” The agent needs to prove it should have access, just like a user does.
For enterprises this is huge. I’ve sat in enough meetings where the security team pushes back on bot projects because there’s no governance model. “How do we control what it does? How do we audit it? How do we revoke access?” With Entra Agent ID, the answers are the same tools they already use for users and workloads. Of course, this also means your agent might get blocked by a Conditional Access policy you didn’t expect, so test early.
A look ahead
One thing that caught my eye in the Ignite announcements was the Agent-to-Agent (A2A) protocol. The idea is that agents can delegate tasks to other agents, and each agent maintains its own scoped identity. So your Teams bot could ask a specialized document-processing agent to summarize a file, and that agent would authenticate with its own blueprint, its own scoped permissions, and its own audit trail. No sharing of tokens, no permission escalation. Every agent in the chain proves its own identity.
We’re not there yet. Entra Agent ID is still in preview and I fully expect some of the API surface to change before GA. The Microsoft.Identity.AgentPlatform namespace might look different six months from now. But the direction is clear, and it’s the right direction.
I started with a client secret stored in a web.config file in 2017. We went from “store a secret and hope for the best” to Managed Identities to actual scoped agent identity with governance built in. It took almost a decade, but bots finally have an identity model that doesn’t feel like a hack.