Agent OAuth flows in Microsoft Entra

Autonomous, on-behalf-of, and agent-user flows, federated identity credentials, and why interactive flows are blocked.

🟦 Module 10 14 min read Not started

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

  1. Enumerate the three agent OAuth flows in Entra and pick the right one per scenario.
  2. Explain why /authorize and public-client flows are blocked for agents.
  3. Use Federated Identity Credentials (FIC) instead of client secrets.
  4. Acquire tokens via the Entra ID Auth SDK (sidecar) — the officially recommended path.
  5. 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.

FlowGrant typeSubjectUse when
Autonomous (app-only)client_credentialsAgent identity (idtyp=agent)The agent runs on its own — schedules, event triggers, background jobs
On-behalf-of (OBO)jwt-bearerUser (idtyp=user) with actor claim referencing the agentInteractive agent acts using the signed-in user's permissions
Agent's user account (impersonation)jwt-bearer via FIC chainAgent user (idtyp=user) with parent-agent linkAgent 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 /authorize flows. 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:

  1. 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.
  2. Certificate — a private key held in Azure Key Vault or HSM, rotated on a schedule.
  3. FIC on external OIDC — trust GitHub Actions, AWS STS, GCP Workload Identity, or SPIFFE / SPIRE trust domains (see Lesson 10.9).
  4. 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)

powershell
# 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 oid
http
GET /AuthorizationHeader/Graph?AgentIdentity=<agent-id-client-id>
Host: sidecar:7000

Response:

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

json
{
  "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.Read

4.2 Sidecar call

http
# 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 oid

You 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

json
{
  "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:

ClaimAutonomousOBOAgent user
idtypagentuseruser
oid / subagent identityend useragent user account
roles (app roles)present, from agent identityusually absentusually absent
scp (delegated scopes)absentpresent, from user delegated grantpresent
xms_ac / actorabsentagent identityagent identity
app_displaynameagent display nameagent display nameagent display name
app_idagent identity client idagent identity client idagent 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

  1. Trying /authorize for an agent. Blocked. Use autonomous or OBO.
  2. Client secret in appsettings.json for prod. Discouraged, but people still do it. FIC + MI, always.
  3. Reusing one FIC across environments. Bind FICs to specific MIs — one per environment.
  4. Forgetting /Validate before calling /AuthorizationHeader in OBO. The sidecar caches invalid tokens if you don't. /Validate is cheap; call it every time.
  5. Providing both AgentUserId and AgentUsername. Validation error — pick one.
  6. Requesting scope=User.Read (v1) when Entra expects scope=https://graph.microsoft.com/User.Read or scope=User.Read /.default. Use v2 scopes.
  7. 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.
  8. 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.

  1. Bind the MI to the blueprint via FIC. Verify with Get-MgApplicationFederatedIdentityCredential.
  2. Deploy the Entra ID Auth SDK sidecar with Docker Compose. Configure with the blueprint client id + FIC.
  3. 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=5 which app-only can do.
  4. 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 /Validate then /AuthorizationHeader/Graph.
    • Agent then calls /me — should succeed and return the user.
  5. Create an agent user account for your agent identity (Lesson 10.2 step 7.4). Wait for mailbox provisioning.
  6. 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.
  7. Decode all three tokens, verify the idtyp, oid, xms_ac claims match the tables above. Save each as token_autonomous.json, token_obo.json, token_agent_user.json — they're gold for future debugging.

11. Self-check

  1. Why does Microsoft block /authorize and public-client flows for agent principals?
  2. Which grant type does the OBO agent flow use? Which does autonomous use?
  3. In an OBO token, whose oid is the sub?
  4. What does an FIC replace, and what does it not replace?
  5. What's the correct sidecar call to obtain an autonomous token vs an agent user token?
  6. Why does OBO risk attribute to the user rather than the agent?

12. References

Sign in to save your progress and earn badges.