Agent OAuth flows in Microsoft Entra
Autonomous, on-behalf-of, and agent-user flows, federated identity credentials, and why interactive flows are blocked.
Why this matters
Every agent action ultimately becomes a token request. Microsoft explicitly removes the flows you're used to (browser-based /authorize, public clients) and enforces three specific patterns for agents. If you skip this lesson you'll try to build a device-code flow for a Copilot Studio agent and lose two days.
Cross-ref: Lesson 3.1 (OAuth flows for agents) covers the standards side (PKCE, device flow, CIBA, token exchange, DPoP, RAR). This lesson is the Microsoft implementation, its restrictions, and its extension (Federated Identity Credentials).
Learning objectives
- Enumerate the three agent OAuth flows in Entra and pick the right one per scenario.
- Explain why
/authorizeand public-client flows are blocked for agents. - Use Federated Identity Credentials (FIC) instead of client secrets.
- Acquire tokens via the Entra ID Auth SDK (sidecar) ā the officially recommended path.
- Read agent tokens and identify the blueprint / identity / user claims.
1. The three (and only three) flows
Microsoft's design principle: all agent authN is programmatic; no browser, ever.
| Flow | Grant type | Subject | Use when |
|---|---|---|---|
| Autonomous (app-only) | client_credentials | Agent identity (idtyp=agent) | The agent runs on its own ā schedules, event triggers, background jobs |
| On-behalf-of (OBO) | jwt-bearer | User (idtyp=user) with actor claim referencing the agent | Interactive agent acts using the signed-in user's permissions |
| Agent's user account (impersonation) | jwt-bearer via FIC chain | Agent user (idtyp=user) with parent-agent link | Agent needs to be a user (mailbox, Teams presence, org-chart membership) |
That's the whole flow catalogue. Anything else you might have tried in Entra for a normal app (device code, ROPC, implicit) is off the table for agent principals.
1.1 Blocked patterns (why they're blocked)
- Interactive
/authorizeflows. Agents are not humans; there is no browser step, so no visual consent screen. All consent happens administratively at the blueprint level and dynamically via the sidecar's programmatic consent APIs. - Public clients. All agent entities are confidential clients. Public-client patterns (device code, native app + no secret) leak into the user's threat model and don't work with the impersonation chain.
- A web redirect URI on a blueprint. Allowed only for consent flows (
response_type=none) ā never for interactive token acquisition. Redirect URIs for user-facing sign-in still belong on the client application, not the agent blueprint.
Microsoft is opinionated here on purpose. When you propose a "device code login" for a Copilot Studio agent, push back: the correct answer is autonomous flow + FIC.
2. Federated Identity Credentials (FIC) ā the credential story
This is the single most important 2025-26 change to Microsoft's agent-auth story: stop using client secrets.
Recommended credential types on a blueprint, in decreasing preference order:
- Managed identity + FIC ā an Azure managed identity (system- or user-assigned) is trusted by the blueprint. The blueprint holds no secret; it validates tokens issued by Azure IMDS.
- Certificate ā a private key held in Azure Key Vault or HSM, rotated on a schedule.
- FIC on external OIDC ā trust GitHub Actions, AWS STS, GCP Workload Identity, or SPIFFE / SPIRE trust domains (see Lesson 10.9).
- Client secret ā production-forbidden by Microsoft's own warnings. Only for dev.
FIC replaces the "how do I ship a secret with the agent" problem with cryptographic trust in the runtime issuer. This is the direct Entra analogue of what you learned about SPIFFE + JWT-Bearer Grant (RFC 7523) in Lesson 2.2.
2.1 Setting up an FIC (managed identity ā blueprint)
# Grant an Azure managed identity permission to impersonate a blueprint
$blueprint = Get-MgApplication -Filter "displayName eq 'MTN CRM Helpdesk Agent'"
$mi = Get-AzUserAssignedIdentity -Name "crm-helpdesk-agent-mi" -ResourceGroupName "rg-agents"
New-MgApplicationFederatedIdentityCredential -ApplicationId $blueprint.Id `
-BodyParameter @{
name = "crm-helpdesk-mi-fic"
issuer = "https://login.microsoftonline.com/$($ctx.TenantId)/v2.0"
subject = "$($mi.PrincipalId)"
audiences = @("api://AzureADTokenExchange")
description = "MTN CRM Helpdesk MI"
}At runtime, code inside the managed-identity-bound pod / Function / Container App calls DefaultAzureCredential, which fetches an IMDS token, exchanges it at Entra for a blueprint token, and hands you a token whose subject is one of the blueprint's child agent identities.
3. Flow 1 ā Autonomous agent (client credentials)
The agent has no user context. It runs to a schedule / reacts to events / does back-office work.
3.1 Sequence
Agent runtime --(1)--> Entra token endpoint
(grant_type=client_credentials
client_id=<agent-identity> <-- agent identity as client
client_assertion=<blueprint FIC JWT> <-- proof it's really the blueprint impersonating
scope=<resource>/.default)
Entra --(2)--> Agent runtime
Access token, subject = agent identity
aud = target resource
idtyp = agent
oid = agent identity oid3.2 Sidecar call (recommended path)
GET /AuthorizationHeader/Graph?AgentIdentity=<agent-id-client-id>
Host: sidecar:7000Response:
Authorization: Bearer eyJ0eXAiOiJKV1Qiā¦The sidecar handles the client_assertion construction using the blueprint's FIC and hides the wire protocol. You never write the token exchange yourself.
3.3 Token snippet you should see
{
"aud": "https://graph.microsoft.com",
"iss": "https://login.microsoftonline.com/<tenant>/v2.0",
"idtyp": "agent",
"oid": "9d6f8e73-...-agent-identity",
"sub": "9d6f8e73-...-agent-identity",
"roles": ["User.Read.All"],
"app_displayname": "Agent-CRM-HelpdeskV2-prod-eu-023"
}Use for: ingestion, scheduled reports, event reactions, back-office cleanup, cross-tenant admin work. Do not use for: any action that requires user-specific consent or user-scoped data access ā Conditional Access won't attribute it to the user, and audit trails collapse.
4. Flow 2 ā On-behalf-of (OBO)
The agent is embedded in a user-facing app. The user signs in ā the app forwards the user token ā the agent exchanges it for a token that can call a different resource on that user's behalf.
4.1 Sequence
Alice signs in to the app (browser / MSAL)
|
| app has user-scoped token (aud = app-api)
v
App -> agent runtime {user_token, "please summarise my mail"}
|
| agent runtime -> sidecar /Validate <-- validate user token first
| sidecar -> Entra jwt-bearer grant
| grant_type = jwt-bearer
| assertion = <user token>
| scope = https://graph.microsoft.com/Mail.Read
| client_id = <agent identity>
| client_assertion = <blueprint FIC JWT>
v
Entra returns access token:
sub = alice's oid
idtyp = user
xms_ac / actor claim = {oid: <agent identity>}
aud = graph
scp = Mail.Read4.2 Sidecar call
# 1. Validate the incoming user token
GET /Validate
Authorization: Bearer <user-token>
# 2. Ask the sidecar for an OBO header targeting Graph
GET /AuthorizationHeader/Graph?AgentIdentity=<agent-identity-client-id>
Authorization: Bearer <user-token>4.3 What Conditional Access and ID Protection see
- Conditional Access applies to Alice's user identity because she is the subject. Policies targeting the agent identity don't fire on OBO. This is important ā if you want to restrict OBO by geography you write a user-targeted CA policy that also filters by the requested resource.
- ID Protection attributes any risky behaviour to Alice (not the agent). Rationale: an OBO risk usually means Alice's session was compromised; disabling the agent globally would break every other user. You remediate at the user session.
Design implication: OBO does not shield the agent from user compromise. If the user is compromised, everything the user could do, the agent can also do (with narrowed scope).
5. Flow 3 ā Agent's user account
Rare, but powerful. The agent has its own user object in the directory and is acting as that person.
5.1 Sequence (impersonation chain)
Agent runtime -> sidecar /AuthorizationHeader/Graph
?AgentIdentity=<agent-id-client-id>
&AgentUserId=<agent-user-object-id>
Sidecar composes a two-step exchange:
a. Blueprint FIC -> Entra -> agent identity token
b. Agent identity token -> Entra (jwt-bearer) -> agent user token
subject = agent user (idtyp=user)
actor / parent claim = agent identity oidYou provide either AgentUserId (Entra oid) or AgentUsername (UPN); providing both is a validation error. AgentIdentity is always required ā it tells the sidecar which parent to impersonate through.
5.2 Token snippet
{
"aud": "https://graph.microsoft.com",
"iss": "https://login.microsoftonline.com/<tenant>/v2.0",
"idtyp": "user",
"oid": "12ab34cd-...-agent-user",
"sub": "12ab34cd-...-agent-user",
"upn": "helpdesk-bot@mtn.co.za",
"app_displayname": "Agent-CRM-HelpdeskV2-prod-eu-023",
"xms_ac": { "oid": "9d6f8e73-...-agent-identity" }
}Use for: agent posts a Teams message that should appear from "@helpdesk-bot"; agent sends an email from its own mailbox; agent joins a Teams meeting as a participant.
Do not use for: things the parent agent identity can already do on its own ā you're adding a whole user object and licence cost to no benefit.
5.3 Why the "impersonation via FIC" chain?
It gives you a cryptographically verifiable trust path: user account ā parent agent identity ā parent blueprint ā tenant. If anything in the chain is compromised (say, an admin marks the parent identity as risky), the user account also can't sign in. Great for containment.
6. Managed identities: the preferred credential type
Microsoft is explicit: managed identities are the recommended credential for blueprints on Azure.
Why:
- No secrets in code / config. Azure runtime injects the identity.
- Automatic rotation. Handled by Azure.
- Secure by default. MI tokens are only fetched from IMDS in the correct process context.
- Native audit. Every MI token acquisition is auditable in Azure activity logs.
Set-up: attach a user-assigned managed identity to the App Service / Function / Container App / VM running the agent, add an FIC on the blueprint that trusts the MI's principal ID, done.
If your agent runs outside Azure, use certificates or an OIDC-federated identity from GitHub / AWS / GCP / SPIFFE (Lesson 10.9).
7. Reading agent tokens quickly
You'll spend hours in the debugger comparing tokens. A checklist:
| Claim | Autonomous | OBO | Agent user |
|---|---|---|---|
idtyp | agent | user | user |
oid / sub | agent identity | end user | agent user account |
roles (app roles) | present, from agent identity | usually absent | usually absent |
scp (delegated scopes) | absent | present, from user delegated grant | present |
xms_ac / actor | absent | agent identity | agent identity |
app_displayname | agent display name | agent display name | agent display name |
app_id | agent identity client id | agent identity client id | agent identity client id |
Rule: idtyp + xms_ac together tell you which of the three flows produced the token. Log both in every audit event.
8. Sidecar architecture pattern
The Entra ID Auth SDK sidecar is a containerised HTTP service that runs alongside your agent. Your agent stays code-agnostic; all identity work happens over localhost:7000.
+----------------+ HTTP +--------------------+
| Agent runtime | -------------------> | Entra Auth SDK |
| (Python / | /Validate | (sidecar, |
| Node / | /AuthorizationHeader | Docker |
| .NET, or | | container) |
| Bedrock / | <------------------- | |
| n8n / Ollama)| Bearer ⦠| |
+----------------+ +----------+---------+
|
| client_assertion (FIC)
v
+--------------------+
| Entra ID token |
| endpoint |
+--------------------+Why this pattern:
- Language-agnostic ā your Python / Node / .NET / any-language agent doesn't need an MSAL SDK.
- Credential isolation ā the FIC + MI never leaves the sidecar; the agent process only sees bearer tokens.
- Uniform behaviour ā the sidecar enforces validation, scope selection, caching, refresh.
- Works locally with Docker Compose; deploys unchanged to Azure Container Apps or Kubernetes.
Deploy patterns: same pod (Kubernetes), same task (ECS), same Compose stack (dev), same Container App (Azure). The sidecar port (7000 by default) is never exposed outside the pod.
Cross-ref: Lesson 10.9 shows the sidecar in production with a Bedrock agent and an n8n workflow.
9. Common pitfalls
- Trying
/authorizefor an agent. Blocked. Use autonomous or OBO. - Client secret in
appsettings.jsonfor prod. Discouraged, but people still do it. FIC + MI, always. - Reusing one FIC across environments. Bind FICs to specific MIs ā one per environment.
- Forgetting
/Validatebefore calling/AuthorizationHeaderin OBO. The sidecar caches invalid tokens if you don't./Validateis cheap; call it every time. - Providing both
AgentUserIdandAgentUsername. Validation error ā pick one. - Requesting
scope=User.Read(v1) when Entra expectsscope=https://graph.microsoft.com/User.Readorscope=User.Read /.default. Use v2 scopes. - Passing the agent identity secret in the assertion instead of the blueprint FIC. Agent identities do not own credentials. If it works, you're actually using an old-style service principal, not an agent identity ā go back to Lesson 10.2.
- Assuming OBO risk lands on the agent. It attributes to the user (see Lesson 10.6). Don't try to fire agent-scoped CA on OBO risk ā target the user.
10. Hands-on lab (3 h)
Prereqs: a test Entra tenant, a user-assigned managed identity in Azure, a blueprint from Lesson 10.2's lab.
- Bind the MI to the blueprint via FIC. Verify with
Get-MgApplicationFederatedIdentityCredential. - Deploy the Entra ID Auth SDK sidecar with Docker Compose. Configure with the blueprint client id + FIC.
- Write a 30-line Python agent that:
- Calls
GET /AuthorizationHeader/Graph?AgentIdentity=<...>for the autonomous flow. - Uses the returned bearer to
GET https://graph.microsoft.com/v1.0/meā expect it to fail (no user context) but with a specific Graph error telling you the token is app-only. - Falls back to
/users?$top=5which app-only can do.
- Calls
- Now do the OBO leg:
- Sign in a test user in a small web page (MSAL.js), send the user token to your agent.
- Agent calls
/Validatethen/AuthorizationHeader/Graph. - Agent then calls
/meā should succeed and return the user.
- Create an agent user account for your agent identity (Lesson 10.2 step 7.4). Wait for mailbox provisioning.
- Do the agent user flow:
- Sidecar
/AuthorizationHeader/Graph?AgentIdentity=ā¦&AgentUserId=ā¦. - Use the token to
POST /me/sendMailā expect an email to arrive from the agent user's mailbox.
- Sidecar
- Decode all three tokens, verify the
idtyp,oid,xms_acclaims match the tables above. Save each astoken_autonomous.json,token_obo.json,token_agent_user.jsonā they're gold for future debugging.
11. Self-check
- Why does Microsoft block
/authorizeand public-client flows for agent principals? - Which grant type does the OBO agent flow use? Which does autonomous use?
- In an OBO token, whose
oidis thesub? - What does an FIC replace, and what does it not replace?
- What's the correct sidecar call to obtain an autonomous token vs an agent user token?
- Why does OBO risk attribute to the user rather than the agent?
12. References
- Authentication protocols in agents
- Entra ID Auth SDK (sidecar) ā token acquisition guide
- Agent user account OAuth flow
- Vendor-neutral Lesson 3.1 ā OAuth flows for agents (device / CIBA / RAR / DPoP baseline).
- Vendor-neutral Lesson 2.2 ā Workload identity, especially JWT Bearer Grant (RFC 7523) which FIC implements.
Sign in to save your progress and earn badges.