<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Anton Staykov</title>
    <description>The latest articles on DEV Community by Anton Staykov (@astaykov).</description>
    <link>https://dev.to/astaykov</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3827606%2Fbb3aa4b8-90b5-4db5-8ad5-f9bd05ab762b.jpg</url>
      <title>DEV Community: Anton Staykov</title>
      <link>https://dev.to/astaykov</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/astaykov"/>
    <language>en</language>
    <item>
      <title>Why consent is the trust contract for Entra Agent ID</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Wed, 12 Aug 2026 11:07:00 +0000</pubDate>
      <link>https://dev.to/astaykov/why-consent-is-the-trust-contract-for-entra-agent-id-c03</link>
      <guid>https://dev.to/astaykov/why-consent-is-the-trust-contract-for-entra-agent-id-c03</guid>
      <description>&lt;p&gt;If your AI agent can read customer mailboxes, create directory objects, or call internal APIs, your real architecture question is not "can it authenticate?" The hard question is "who authorized this, under which policy, for how long, and with which guardrails?" In Microsoft Entra, that question is answered through &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview" rel="noopener noreferrer"&gt;permissions and consent&lt;/a&gt;. That is why consent is not a user-experience detail. It is the trust contract.&lt;/p&gt;

&lt;p&gt;This matters even more for agent identities. Microsoft Entra Agent ID deliberately treats agents as a special operational class, with explicit authorization controls and blocked high-risk privileges rather than unconstrained app-like power (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/authorization-agent-id" rel="noopener noreferrer"&gt;authorization model&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/agentid-platform-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Graph overview for Agent ID&lt;/a&gt;). If your mental model is still "grant broad access once and move on," your governance will fail faster than your pilot succeeds.&lt;/p&gt;

&lt;p&gt;In [[A-transformational-AI-agent-does-not-start-with-the-permissions-it-needs]], I argued that strong agent platforms start narrow and earn scope in context. This article is the governance layer under that claim: why consent is the control surface, how to operate it at enterprise scale, and how access packages turn theory into an auditable runtime model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consent is authorization, not authentication
&lt;/h2&gt;

&lt;p&gt;Consent in Entra is the act that authorizes an application identity to access a protected resource with specific permissions (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview" rel="noopener noreferrer"&gt;user and admin consent overview&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview" rel="noopener noreferrer"&gt;permissions and consent overview&lt;/a&gt;). Authentication answers "who are you?" Consent answers "what are you allowed to do against this API or resource?"&lt;/p&gt;

&lt;p&gt;That distinction sounds obvious until production pressure arrives. Teams often secure sign-in, configure Conditional Access, and then assume the authorization problem is solved. It is not. A signed-in actor without the right consent grant is still unauthorized, and a correctly consented actor can still be over-privileged if governance posture is weak (&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview" rel="noopener noreferrer"&gt;permissions and consent overview&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent?pivots=portal" rel="noopener noreferrer"&gt;configure user consent&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For mixed technical leadership, this is the key reframing: consent is where legal risk, security posture, and delivery velocity intersect. It is the formal boundary between "agent capability requested" and "agent capability approved."&lt;/p&gt;

&lt;h2&gt;
  
  
  User consent and admin consent are different risk gates
&lt;/h2&gt;

&lt;p&gt;Entra supports delegated permissions where an app or agent acts on behalf of a signed-in user, and application permissions where it acts autonomously without a user context (&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview#types-of-permissions" rel="noopener noreferrer"&gt;permissions and consent overview&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/grant-agent-access-microsoft-365" rel="noopener noreferrer"&gt;grant agents access&lt;/a&gt;). Those two modes map to different consent paths and different blast radii.&lt;/p&gt;

&lt;p&gt;User consent is viable when non-admin users are allowed to consent to the app and permission set by tenant policy (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview#user-consent" rel="noopener noreferrer"&gt;user and admin consent overview&lt;/a&gt;). Admin consent is required for application permissions and for delegated permissions that are admin-restricted or otherwise outside permitted user-consent boundaries (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview#admin-consent" rel="noopener noreferrer"&gt;user and admin consent overview&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/grant-agent-access-microsoft-365" rel="noopener noreferrer"&gt;grant agents access&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That is not bureaucracy. That is architecture. User consent is typically scoped to the user context. Admin consent can authorize tenant-wide impact. Treating those as interchangeable is how over-privileged agents are born.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consent policy is a leadership decision encoded in platform behavior
&lt;/h2&gt;

&lt;p&gt;Most organizations do not leave consent wide open, and they should not. Entra gives administrators tenant-wide choices: disable user consent entirely, allow only verified publishers plus selected low-impact permissions, or allow broader user consent where risk tolerance supports it (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent?pivots=portal" rel="noopener noreferrer"&gt;configure user consent&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview#user-consent-settings" rel="noopener noreferrer"&gt;user and admin consent overview&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If user consent is limited, Entra can route blocked requests through the admin consent workflow so business demand is visible and reviewable instead of hidden in shadow tooling (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview#admin-consent-workflow" rel="noopener noreferrer"&gt;user and admin consent overview&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-admin-consent-workflow" rel="noopener noreferrer"&gt;configure admin consent workflow&lt;/a&gt;). This is where governance stops being a "no" function and becomes a control function.&lt;/p&gt;

&lt;p&gt;For leadership teams, the operational takeaway is clear: consent policy is not a tactical identity setting. It is a policy expression of enterprise risk appetite. Decide it explicitly, document it, and align agent deployment patterns to it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consent phishing proves whether your model is real
&lt;/h2&gt;

&lt;p&gt;Consent phishing is the attack pattern where users are tricked into granting OAuth permissions to malicious applications, often through convincing app names and realistic prompts (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/protect-against-consent-phishing#what-is-consent-phishing" rel="noopener noreferrer"&gt;protect against consent phishing&lt;/a&gt;). This is not theoretical noise. It is exactly what happens when the organization treats consent as a user-interface click instead of a control plane.&lt;/p&gt;

&lt;p&gt;Microsoft guidance is explicit: harden user consent settings, favor verified publishers, audit app permissions, investigate suspicious grants, and integrate detection and remediation workflows (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/protect-against-consent-phishing#best-practices-for-hardening-against-consent-phishing-attacks" rel="noopener noreferrer"&gt;protect against consent phishing&lt;/a&gt;). If those controls are absent, your "agent security strategy" is just language.&lt;/p&gt;

&lt;p&gt;A mature model assumes two truths at once: legitimate agents need controlled permission growth, and adversaries will try to exploit the same consent mechanics. Governance has to handle both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Delegated vs application permissions for agents, with protocol constructs that matter
&lt;/h2&gt;

&lt;p&gt;For agent identities, delegated mode is used when the agent acts in a user context. Application mode is used when the agent acts autonomously (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/authorization-agent-id#microsoft-graph-permissions-for-agent-ids" rel="noopener noreferrer"&gt;authorization in Agent ID&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/grant-agent-access-microsoft-365#when-to-use-delegated-or-application-permissions" rel="noopener noreferrer"&gt;grant agents access&lt;/a&gt;). That split directly affects token semantics.&lt;/p&gt;

&lt;p&gt;In delegated scenarios, effective scopes are carried in the &lt;code&gt;scp&lt;/code&gt; claim. In app-only scenarios, effective app permissions are carried in the &lt;code&gt;roles&lt;/code&gt; claim (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-token-claims#core-token-claim-types" rel="noopener noreferrer"&gt;token claims for agents&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/grant-agent-access-microsoft-365#when-to-use-delegated-or-application-permissions" rel="noopener noreferrer"&gt;grant agents access&lt;/a&gt;). You do not need packet-level protocol traces to govern correctly, but you do need leadership alignment that these constructs define runtime authority.&lt;/p&gt;

&lt;p&gt;The advanced nuance for Agent ID is permission inheritance at blueprint level. This is not default behavior. Entra documents that inheritance only happens when you explicitly configure inheritable permissions on the blueprint and the corresponding permissions are actually granted on the blueprint principal (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/concept-inheritable-permissions" rel="noopener noreferrer"&gt;inheritable permissions concept&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/configure-inheritable-permissions-blueprints" rel="noopener noreferrer"&gt;configure inheritable permissions&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That should drive a stricter approval posture: treat blueprint consent as baseline capability setup, not as a full production access request. Keep blueprint consent narrow, and push broader or time-bound operational access through governed assignment workflows such as access packages (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-access-packages" rel="noopener noreferrer"&gt;access packages for agent identities&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Agent ID already blocks high-risk permission paths, by design
&lt;/h2&gt;

&lt;p&gt;A healthy governance discussion should also acknowledge built-in platform guardrails. Agent IDs can use delegated and application permission models, but Entra explicitly blocks a range of high-risk Graph permissions for agents and restricts role assignment options compared to unconstrained identity types (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/authorization-agent-id" rel="noopener noreferrer"&gt;authorization in Agent ID&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/agentid-platform-overview?view=graph-rest-1.0#microsoft-graph-permissions-blocked-for-agents" rel="noopener noreferrer"&gt;Agent ID Graph overview blocked permissions&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This design choice reinforces the principle of least privilege for autonomous systems. It does not remove governance responsibility, but it reduces failure modes where someone "temporarily" grants sweeping directory control and forgets to remove it.&lt;/p&gt;

&lt;p&gt;In plain terms: the platform is already telling you the right operating model. Narrow by default. Escalate deliberately. Keep high-risk paths off the table unless there is an explicit and supported control route.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access packages are the practical admin-consent engine for agent scale
&lt;/h2&gt;

&lt;p&gt;Consent alone is necessary, but not sufficient, when you run fleets of agents. You need a repeatable, policy-owned mechanism for requesting, approving, granting, expiring, and reviewing access. For Entra Agent ID, access packages provide that mechanism (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-access-packages" rel="noopener noreferrer"&gt;access packages for agent identities&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/agent-id-governance-overview" rel="noopener noreferrer"&gt;governing agent identities&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is why I keep connecting this to [[Access-Packages-Are-the-Control-Plane-for-Entra-Agent-ID-Permissions]]. Access packages let you operationalize consent and authorization as workflow, not as ad hoc privilege assignment. They can include directory roles, OAuth delegated and application permissions, and group memberships in one governed package (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-access-packages" rel="noopener noreferrer"&gt;access packages for agent identities&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Crucially, policy can require one, two, or three approval stages, with explicit approver definitions and decision timeouts (&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-approval-policy" rel="noopener noreferrer"&gt;approval policy settings&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-create#specify-approval-settings" rel="noopener noreferrer"&gt;access package create stages&lt;/a&gt;). That means you can model governance depth based on data sensitivity, not gut feel.&lt;/p&gt;

&lt;h2&gt;
  
  
  Dynamic approvals, SoD checks, and custom logic make governance adaptive
&lt;/h2&gt;

&lt;p&gt;Static approver lists are often too rigid for enterprise reality. Entra entitlement management supports dynamic approval determination through custom extensions and Logic Apps integration, so approvers can be derived through business logic at request time (&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-dynamic-approval" rel="noopener noreferrer"&gt;dynamic approval&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-logic-apps-integration" rel="noopener noreferrer"&gt;Logic Apps integration&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Separation-of-duties controls let you mark access packages or groups as incompatible, preventing users or identities with one assignment from requesting conflicting assignments (&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-incompatible" rel="noopener noreferrer"&gt;SoD checks&lt;/a&gt;). This is the difference between "we approved safely once" and "we remain safe over time."&lt;/p&gt;

&lt;p&gt;If you need one sentence for the board slide, use this: access packages are not just an approval screen. They are a programmable authorization lifecycle with governance state.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical operating model for mixed technical leadership
&lt;/h2&gt;

&lt;p&gt;If you lead a platform team, security team, or product portfolio touching agent identities, this model is battle-tested and aligned to Microsoft guidance:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Start with narrow required access&lt;/strong&gt; on blueprints and explicit inheritable-permission design (inheritance is opt-in, not automatic), so admins can review baseline intent clearly and avoid turning blueprint consent into broad standing access (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/concept-inheritable-permissions" rel="noopener noreferrer"&gt;inheritable permissions concept&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/configure-inheritable-permissions-blueprints" rel="noopener noreferrer"&gt;configure inheritable permissions&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Set tenant consent policy intentionally&lt;/strong&gt; (usually verified-publisher plus low-risk where possible), and force higher-risk asks through admin review paths (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent?pivots=portal" rel="noopener noreferrer"&gt;configure user consent&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview" rel="noopener noreferrer"&gt;user and admin consent overview&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Route runtime growth through access packages&lt;/strong&gt; instead of direct permanent grants, including approver stages, SoD, and expiry settings (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-access-packages" rel="noopener noreferrer"&gt;access packages for agent identities&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-approval-policy" rel="noopener noreferrer"&gt;approval policy&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-incompatible" rel="noopener noreferrer"&gt;SoD checks&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Treat sponsor and owner accountability as first-class controls&lt;/strong&gt;, because governance is not complete if no human is accountable for extension and renewal decisions (&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/agent-id-governance-overview" rel="noopener noreferrer"&gt;governing agent identities&lt;/a&gt;).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Assume consent abuse attempts and instrument for response&lt;/strong&gt;, including consent-phishing hardening and suspicious-grant investigation workflows (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/protect-against-consent-phishing" rel="noopener noreferrer"&gt;protect against consent phishing&lt;/a&gt;).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This operating model gives engineering enough autonomy to ship and iterate, while preserving the control evidence leadership needs for audit and risk committees.&lt;/p&gt;

&lt;h2&gt;
  
  
  The core decision
&lt;/h2&gt;

&lt;p&gt;You can run agents on standing privilege and hope nothing goes wrong. Many teams do exactly that during pilots. It looks fast right until the first real governance review.&lt;/p&gt;

&lt;p&gt;Or you can run agents on governed consent, with policy-owned approval paths, explicit token authority constructs, and time-bound assignment lifecycle. That model scales beyond demos because it aligns with how Entra is designed to authorize applications and agents (&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview" rel="noopener noreferrer"&gt;permissions and consent overview&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/authorization-agent-id" rel="noopener noreferrer"&gt;authorization in Agent ID&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-access-packages" rel="noopener noreferrer"&gt;access packages for agent identities&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That is the underrated shift in enterprise agent architecture right now: the winning platform is not the one with the most tools. It is the one where consent remains the trust contract, even as agent capability grows.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/authorization-agent-id" rel="noopener noreferrer"&gt;Authorization in Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/grant-agent-access-microsoft-365" rel="noopener noreferrer"&gt;Grant agents access to Microsoft 365 resources&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-token-claims" rel="noopener noreferrer"&gt;Token claims reference for agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/concept-inheritable-permissions" rel="noopener noreferrer"&gt;Inheritable permissions and required resource access in Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/configure-inheritable-permissions-blueprints" rel="noopener noreferrer"&gt;Configure inheritable permissions for agent identity blueprints&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/permissions-consent-overview" rel="noopener noreferrer"&gt;Overview of permissions and consent in the Microsoft identity platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview" rel="noopener noreferrer"&gt;Overview of user and admin consent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent?pivots=portal" rel="noopener noreferrer"&gt;Configure how users consent to applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-admin-consent-workflow" rel="noopener noreferrer"&gt;Configure the admin consent workflow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/protect-against-consent-phishing" rel="noopener noreferrer"&gt;Protect against consent phishing&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-access-packages" rel="noopener noreferrer"&gt;Access packages for Agent identities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/agent-id-governance-overview" rel="noopener noreferrer"&gt;Governing Agent Identities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-approval-policy" rel="noopener noreferrer"&gt;Change approval and requestor information settings for an access package in entitlement management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-create" rel="noopener noreferrer"&gt;Create an access package in entitlement management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-dynamic-approval" rel="noopener noreferrer"&gt;Externally determine the approval requirements for an access package using custom extensions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-logic-apps-integration" rel="noopener noreferrer"&gt;Trigger Logic Apps with custom extensions in entitlement management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-incompatible" rel="noopener noreferrer"&gt;Configure separation of duties checks for an access package in entitlement management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/agentid-platform-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Microsoft Entra Agent ID APIs in Microsoft Graph overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>entraagentid</category>
      <category>identitygovernance</category>
      <category>microsoftentra</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>From JWT-SVID to Entra Agent ID: a working SPIFFE PoC</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Thu, 06 Aug 2026 11:19:00 +0000</pubDate>
      <link>https://dev.to/astaykov/from-jwt-svid-to-entra-agent-id-a-working-spiffe-poc-ic4</link>
      <guid>https://dev.to/astaykov/from-jwt-svid-to-entra-agent-id-a-working-spiffe-poc-ic4</guid>
      <description>&lt;p&gt;The architecture diagram was the easy part.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/astaykov/your-spiffe-workload-can-authenticate-as-an-entra-agent-id-but-mind-the-trust-gap-3969"&gt;Your SPIFFE workload can authenticate as an Entra Agent ID, but mind the trust gap&lt;/a&gt;, I argued that SPIFFE and Microsoft Entra Agent ID belong on opposite sides of a narrow trust contract. SPIRE proves which workload is running. Entra decides which governed agent identity that workload may use. A federated identity credential connects those statements through an exact issuer, subject, and audience match, which is how &lt;a href="https://learn.microsoft.com/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Microsoft Entra workload identity federation validates an external JWT&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That article described the architecture and its trust gap. This one runs it.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://github.com/astaykov/agentid-spiffe" rel="noopener noreferrer"&gt;&lt;code&gt;agentid-spiffe&lt;/code&gt; proof of concept&lt;/a&gt; deploys a complete agent loop with SPIRE, public OIDC discovery, Microsoft Entra Agent ID, and a downstream Microsoft MCP Server for enterprise call. The interesting result is not the chat interface or the model. It is the moment a JWT-SVID issued to one attested process becomes the client assertion for an Agent Identity Blueprint, without a client secret or certificate copied into the workload.&lt;/p&gt;

&lt;p&gt;This is a conceptual walkthrough with selected configuration and code. It is deliberately not a SPIRE deployment guide. Follow the &lt;a href="https://github.com/astaykov/agentid-spiffe#readme" rel="noopener noreferrer"&gt;repository README&lt;/a&gt; to run the sample. Here, we are going to trace the identity as it changes hands.&lt;/p&gt;

&lt;h2&gt;
  
  
  Five SPIFFE concepts in one minute
&lt;/h2&gt;

&lt;p&gt;A &lt;em&gt;trust domain&lt;/em&gt; is the administrative and cryptographic boundary within which SPIFFE identities are issued. Its name forms the authority portion of every SPIFFE ID. The PoC uses &lt;code&gt;poc.local&lt;/code&gt;, so its workload identity begins with &lt;code&gt;spiffe://poc.local/&lt;/code&gt;. SPIFFE defines that URI as the stable name of a workload, independent of an IP address, host name, or cloud provider account (&lt;a href="https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/" rel="noopener noreferrer"&gt;SPIFFE concepts&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The &lt;em&gt;SPIRE Server&lt;/em&gt; is the authority for that trust domain. It stores registration entries, evaluates attestation results, and signs the identity documents issued inside the domain. The &lt;em&gt;SPIRE Agent&lt;/em&gt; runs near workloads, attests to the server as a node, exposes the local Workload API, and performs workload attestation before returning identity documents to a process (&lt;a href="https://spiffe.io/docs/latest/spire-about/spire-concepts/" rel="noopener noreferrer"&gt;SPIRE concepts&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;&lt;em&gt;Workload attestation&lt;/em&gt; is the decision that connects runtime evidence to a registered SPIFFE ID. In this sample, the SPIRE Server registers a Unix selector for a process running as UID 0. When the FastAPI process calls the local Workload API, the SPIRE Agent evaluates that evidence and returns the identity registered for the matching selector. SPIRE documents selectors as the attested properties used to match a workload to a registration entry (&lt;a href="https://spiffe.io/docs/latest/deploying/registering/" rel="noopener noreferrer"&gt;registering workloads&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The returned identity can take two common forms. An X.509-SVID is a certificate suited to mutual TLS. A &lt;em&gt;JWT-SVID&lt;/em&gt; is a signed bearer token whose &lt;code&gt;sub&lt;/code&gt; is the workload's SPIFFE ID and whose &lt;code&gt;aud&lt;/code&gt; names the intended recipient (&lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md" rel="noopener noreferrer"&gt;JWT-SVID specification&lt;/a&gt;). This PoC needs the JWT form because Microsoft Entra accepts an external JWT as the client assertion in its &lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-client-creds-grant-flow#third-case-access-token-request-with-a-federated-credential" rel="noopener noreferrer"&gt;federated client credential flow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That is all the SPIFFE theory required for this walkthrough. The rest is the handoff.&lt;/p&gt;

&lt;h2&gt;
  
  
  The components in play
&lt;/h2&gt;

&lt;p&gt;The PoC places several processes in one Azure Container App image so the identity path is easy to inspect. That packaging is a lab convenience, not an architectural recommendation (&lt;a href="https://github.com/astaykov/agentid-spiffe#architecture" rel="noopener noreferrer"&gt;repository architecture&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Inside the container, five components matter:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The SPIRE Server owns the &lt;code&gt;poc.local&lt;/code&gt; trust domain and signs JWT-SVIDs.&lt;/li&gt;
&lt;li&gt;The SPIRE Agent exposes a Unix socket implementing the Workload API.&lt;/li&gt;
&lt;li&gt;The SPIRE OIDC Discovery Provider publishes the JWT signing keys.&lt;/li&gt;
&lt;li&gt;FastAPI retrieves the JWT-SVID and performs the Entra token exchanges.&lt;/li&gt;
&lt;li&gt;The browser SPA supplies an incoming user token when the sample runs in user-context mode.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Outside the container, Microsoft Entra holds the agent identity blueprint, its blueprint principal, the child agent identity, the federated identity credential, and the delegated grants. The final Agent ID token is passed to the Microsoft MCP Server for enterprise. The model call proves that the token is usable, but it is downstream of the identity mechanism we care about.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPIFFE workload plane                     Microsoft Entra control plane

+---------------------------+            +-----------------------------+
| FastAPI agent workload    |            | Agent Identity Blueprint    |
+---------+-----------------+            +-------------------+---------+
          |                                                  |
          | runtime evidence                                 |
          v                                                  |
+---------+-----------+                                      | Federated Identity
| SPIRE Agent         |                                      | Credential
| Workload API        |                                      |
+-------+-------------+                                      |
        | attestation                                        |
        v                                                    |
+-------+-----------------+      signing keys       +--------+--------+
| SPIRE Server            |------------------------&amp;gt;| OIDC metadata   |
| issues JWT-SVID         |                         | and public JWKS |
+-----------+-------------+                         +-------+---------+
            |                                               ^
            | short-lived JWT-SVID                          |
            v                                               | validates
+-----------+-------------+  JWT-SVID + fmi_path            |
| FastAPI agent workload  |--------------------------------&amp;gt;|
+-------+-----------------+                                 |
        ^                                                   |
        | T1 exchange token                                 |
        +---------------------------------------------------+
        |
        | T1 as child assertion
        v
+-------------+-------------+                +-----------------------------+
| Child agent identity      |---------------&amp;gt;| Downstream MCP resource     |
| requests resource token   |    Agent ID    | validates Entra token       |
+---------------------------+    token       +-----------------------------+
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The diagram contains two identity planes. The SPIFFE plane ends when the workload receives its JWT-SVID. The Entra plane begins when that JWT is presented as the blueprint's client assertion.&lt;/p&gt;

&lt;h2&gt;
  
  
  How the process becomes a SPIFFE workload
&lt;/h2&gt;

&lt;p&gt;The container entrypoint starts the SPIRE Server, creates a join token for the co-located agent, and registers one workload entry (&lt;a href="https://github.com/astaykov/agentid-spiffe/blob/main/src/entrypoint.sh" rel="noopener noreferrer"&gt;entrypoint source&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;spire-server entry create &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-parentID&lt;/span&gt; spiffe://poc.local/agent/node1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-spiffeID&lt;/span&gt; spiffe://poc.local/agent/identity-1 &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;-selector&lt;/span&gt; unix:uid:0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that registration from right to left. A process matching &lt;code&gt;unix:uid:0&lt;/code&gt;, attested through the registered parent agent, receives &lt;code&gt;spiffe://poc.local/agent/identity-1&lt;/code&gt; as its workload identity. The SPIFFE ID is not generated from the process name or container name. It is the explicit result of registration policy and attested selectors, which is the model SPIRE uses to issue identities through its Workload API (&lt;a href="https://spiffe.io/docs/latest/deploying/registering/" rel="noopener noreferrer"&gt;SPIRE workload registration&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The selector is intentionally broad because every process in the image runs in the same lab boundary. A production deployment would use workload and node attestors that express the real platform boundary. That decision is outside this PoC. We only need a reliably attested process from which to request the JWT-SVID.&lt;/p&gt;

&lt;p&gt;The server configuration then defines the JWT issuer and signing profile (&lt;a href="https://github.com/astaykov/agentid-spiffe/blob/main/src/spire/server.conf" rel="noopener noreferrer"&gt;SPIRE server configuration in the PoC&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="nx"&gt;server&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="nx"&gt;trust_domain&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"poc.local"&lt;/span&gt;
    &lt;span class="nx"&gt;jwt_issuer&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"__OIDC_ISSUER_URL__"&lt;/span&gt;
    &lt;span class="nx"&gt;jwt_key_type&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"rsa-2048"&lt;/span&gt;
    &lt;span class="nx"&gt;default_jwt_svid_ttl&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"10m"&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;trust_domain&lt;/code&gt; controls the SPIFFE ID namespace. &lt;code&gt;jwt_issuer&lt;/code&gt; becomes the JWT-SVID &lt;code&gt;iss&lt;/code&gt; claim. &lt;code&gt;jwt_key_type&lt;/code&gt; makes SPIRE sign the assertion with RSA so that the token uses RS256. These values are related, but only the issuer crosses directly into the Entra configuration.&lt;/p&gt;

&lt;p&gt;And this is where the most important interoperability detail appears.&lt;/p&gt;

&lt;h2&gt;
  
  
  The issuer is a public protocol surface, not a string
&lt;/h2&gt;

&lt;p&gt;It is tempting to treat the federated identity credential's &lt;code&gt;issuer&lt;/code&gt; as an identifier. Configure the same URL in SPIRE and Entra, make the strings match, and move on.&lt;/p&gt;

&lt;p&gt;That is not enough.&lt;/p&gt;

&lt;p&gt;Microsoft Entra documents two requirements that make the issuer an operational endpoint. First, RS256 is the supported signing algorithm for workload identity federation assertions. Other algorithms might work, but Microsoft does not test them. A robust implementation therefore needs an RS256-signed JWT, which is why this PoC explicitly configures &lt;code&gt;jwt_key_type = "rsa-2048"&lt;/code&gt; (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations#supported-signing-algorithms-and-issuers" rel="noopener noreferrer"&gt;supported signing algorithms and issuers&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Second, the issuer must be a publicly reachable HTTPS URL that follows OIDC discovery. Microsoft Entra uses the configured issuer to retrieve the metadata and signing keys needed to validate the external token (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust#other-identity-providers-example" rel="noopener noreferrer"&gt;configure trust with another identity provider&lt;/a&gt;). For the issuer used by this PoC, the primary discovery URL is formed by appending &lt;code&gt;/.well-known/openid-configuration&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Issuer
https://&amp;lt;container-app-host&amp;gt;/spiffe-oidc

Discovery document
https://&amp;lt;container-app-host&amp;gt;/spiffe-oidc/.well-known/openid-configuration
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The discovery document points Entra to the public key set:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;container-app-host&amp;gt;/spiffe-oidc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"jwks_uri"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;container-app-host&amp;gt;/spiffe-oidc/keys"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject_types_supported"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"public"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Entra follows &lt;code&gt;jwks_uri&lt;/code&gt;, finds the public key whose &lt;code&gt;kid&lt;/code&gt; matches the JWT-SVID header, and uses that key to verify the RS256 signature. The private signing key remains inside SPIRE. OIDC discovery publishes only the public half required by verifiers, following the standard discovery and JWKS model (&lt;a href="https://openid.net/specs/openid-connect-discovery-1_0.html" rel="noopener noreferrer"&gt;OpenID Connect Discovery 1.0&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is why an exact &lt;code&gt;iss&lt;/code&gt; match can still fail. The URL might be spelled correctly while the discovery document is missing, inaccessible, malformed, or pointing at a JWKS that does not contain the active signing key. In that state, Entra knows &lt;em&gt;where&lt;/em&gt; the assertion claims to come from but cannot cryptographically prove that the issuer signed it.&lt;/p&gt;

&lt;p&gt;The sample runs SPIRE's OIDC Discovery Provider locally, then proxies its metadata and key set through the Container App's public HTTPS origin (&lt;a href="https://github.com/astaykov/agentid-spiffe/blob/main/src/backend/main.py" rel="noopener noreferrer"&gt;OIDC proxy implementation&lt;/a&gt;). The proxy rewrites &lt;code&gt;issuer&lt;/code&gt; and &lt;code&gt;jwks_uri&lt;/code&gt; to their public values, removes an empty authorization endpoint, populates &lt;code&gt;subject_types_supported&lt;/code&gt;, and adds &lt;code&gt;"use": "sig"&lt;/code&gt; to each published key for compatibility. It exposes both the OpenID Connect discovery path shown above and the RFC 8414 path form for an issuer containing a path:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;https://&amp;lt;container-app-host&amp;gt;/.well-known/openid-configuration/spiffe-oidc
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Both documents describe the same issuer and point to the same JWKS. That extra route avoids relying on ambiguous discovery behavior when the issuer itself contains &lt;code&gt;/spiffe-oidc&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The OIDC endpoint is not incidental web plumbing. It is part of the credential chain. If it disappears or publishes the wrong key, the JWT-SVID stops being a credential Entra can validate.&lt;/p&gt;

&lt;h2&gt;
  
  
  The federated identity credential binds one workload to the blueprint
&lt;/h2&gt;

&lt;p&gt;Once the public issuer works, the post-deployment configuration creates a federated identity credential on the &lt;em&gt;agent identity blueprint application&lt;/em&gt;. It does not create the credential on the child agent identity (&lt;a href="https://github.com/astaykov/agentid-spiffe/blob/main/scripts/configure-postdeploy.ps1" rel="noopener noreferrer"&gt;FIC configuration source&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The effective object is small:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spire-jwt-svid-blueprint"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://&amp;lt;container-app-host&amp;gt;/spiffe-oidc"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe://poc.local/agent/identity-1"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"audiences"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"api://AzureADTokenExchange"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Here is a decoded JWT-SVID captured during one of the PoC runs. The signature is omitted, but the header and payload are unchanged:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;{
  "alg": "RS256",
  "kid": "KNd3DSGQAvCP1U2rHUd3rBzf9oX1y7BG",
  "typ": "JWT"
}.{
  "aud": [
    "api://AzureADTokenExchange"
  ],
  "exp": 1785157382,
  "iat": 1785156782,
  "iss": "https://spiffe.proudhill-d482409c.westeurope.azurecontainerapps.io/spiffe-oidc",
  "sub": "spiffe://poc.local/agent/identity-1"
}.[Signature]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The header says that SPIRE signed the assertion with RS256 and identifies the public verification key through &lt;code&gt;kid&lt;/code&gt;. The payload names Entra's token exchange as the only audience, carries a ten-minute validity window, exposes the public OIDC issuer, and uses the registered SPIFFE ID as its subject. Entra retrieves the issuer's JWKS, selects &lt;code&gt;KNd3DSGQAvCP1U2rHUd3rBzf9oX1y7BG&lt;/code&gt;, verifies the omitted signature, and then evaluates the three FIC claim matches (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation#how-it-works" rel="noopener noreferrer"&gt;workload identity federation mechanics&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Those three trust properties map directly to the incoming JWT-SVID:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;JWT-SVID iss  == FIC issuer
JWT-SVID sub  == FIC subject
JWT-SVID aud  == FIC audiences[0]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Microsoft Entra compares those values exactly and case-sensitively when evaluating a standard federated identity credential (&lt;a href="https://learn.microsoft.com/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0#how-do-federated-identity-credentials-work" rel="noopener noreferrer"&gt;federated identity credential matching&lt;/a&gt;). The FIC therefore says something precise: a token from this public issuer, for this one SPIFFE workload, minted specifically for Azure AD token exchange, may authenticate this blueprint.&lt;/p&gt;

&lt;p&gt;It does not grant the workload Microsoft Graph permissions. It does not authorize the model to use an MCP tool. It only replaces the blueprint's stored client secret or certificate with a short-lived external assertion. Authorization remains attached to the child agent identity and the downstream resource, consistent with the &lt;a href="https://learn.microsoft.com/entra/agent-id/agent-identities" rel="noopener noreferrer"&gt;Agent ID identity and authorization model&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The application requests the JWT-SVID only when MSAL needs it
&lt;/h2&gt;

&lt;p&gt;The FastAPI application opens the local SPIRE Workload API through the SPIRE Agent's Unix socket. Its assertion provider asks for one JWT-SVID with &lt;code&gt;api://AzureADTokenExchange&lt;/code&gt; as the audience (&lt;a href="https://github.com/astaykov/agentid-spiffe/blob/main/src/backend/main.py" rel="noopener noreferrer"&gt;backend source&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;SpiffeJwtProvider&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;__call__&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;svid&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;_ensure_source&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;fetch_svid&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="n"&gt;audience&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="n"&gt;self&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;_audience&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
        &lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;svid&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That audience matters. The &lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md#72-audience" rel="noopener noreferrer"&gt;JWT-SVID specification recommends a single audience&lt;/a&gt; because a JWT-SVID is a bearer token and every listed audience becomes a potential recipient. This token exists for one purpose: presentation to Entra's workload identity federation endpoint.&lt;/p&gt;

&lt;p&gt;The provider is passed to MSAL as a callable client assertion rather than as a static token:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;blueprint_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ConfidentialClientApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;BLUEPRINT_CLIENT_ID&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client_credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_assertion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;_jwt_svid_provider&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AUTHORITY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;MSAL can invoke a callable assertion provider when it needs a fresh credential, which fits a short-lived SVID better than capturing one token at process startup (&lt;a href="https://learn.microsoft.com/python/api/msal/msal.application.confidentialclientapplication" rel="noopener noreferrer"&gt;MSAL Python confidential client application&lt;/a&gt;). SPIRE remains responsible for issuing the workload credential. MSAL remains responsible for Entra token acquisition and caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  &lt;code&gt;fmi_path&lt;/code&gt; turns blueprint authentication into one agent identity
&lt;/h2&gt;

&lt;p&gt;The first Entra request authenticates the blueprint with the JWT-SVID and asks for an exchange token scoped to one child agent identity:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blueprint_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire_token_for_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api://AzureADTokenExchange/.default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;fmi_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_app_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;fmi_path&lt;/code&gt; value is the child agent identity's client ID. Microsoft documents that parameter as the instruction telling Entra which agent identity the blueprint is acting on behalf of (&lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow#request-a-token-for-the-agent-identity-blueprint" rel="noopener noreferrer"&gt;request a token for the agent identity blueprint&lt;/a&gt;). The result is T1, an exchange token bound to the blueprint-to-child path.&lt;/p&gt;

&lt;p&gt;The child agent identity has no independent client secret, certificate, or FIC. The application creates a second MSAL confidential client for the child and supplies T1 through another assertion callback:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;assertion_cb&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ctx&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;-&amp;gt;&lt;/span&gt; &lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;blueprint_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire_token_for_client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
        &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api://AzureADTokenExchange/.default&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;fmi_path&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;agent_app_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;result&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;access_token&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;agent_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;msal&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nc"&gt;ConfidentialClientApplication&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;agent_app_id&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;client_credential&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;client_assertion&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;assertion_cb&lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;
    &lt;span class="n"&gt;authority&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;AUTHORITY&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;From there, the flow can branch. In app-only mode, the child requests a resource token using its application permissions. In the PoC's user-context mode, it combines T1 with the incoming user's access token in an on-behalf-of exchange. Microsoft documents that the second request switches &lt;code&gt;client_id&lt;/code&gt; from the blueprint to the child agent identity and presents both the blueprint exchange token and user assertion (&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-on-behalf-of-oauth-flow#protocol-steps" rel="noopener noreferrer"&gt;Agent ID OBO protocol&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The JWT-SVID has already done its job before that branch. It authenticated the blueprint. T1 then authenticated the child agent identity. The final access token is scoped to the downstream resource and represents either the autonomous agent or the agent acting in the user's context, depending on the selected flow (&lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow" rel="noopener noreferrer"&gt;authenticate and acquire tokens for autonomous agents&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That separation is the reason this is more than generic workload federation. The external workload does not become an ordinary app registration. It enters a blueprint-controlled path and emerges as a governed child agent identity.&lt;/p&gt;

&lt;h2&gt;
  
  
  What the PoC proves
&lt;/h2&gt;

&lt;p&gt;The sample proves four useful things.&lt;/p&gt;

&lt;p&gt;First, a JWT-SVID can serve as the real client assertion for an Entra Agent ID blueprint. Microsoft lists SPIFFE and SPIRE as a supported workload identity federation scenario, and Agent ID supports direct workload identity exchange for third-party agent platforms (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation#supported-scenarios" rel="noopener noreferrer"&gt;supported workload identity federation scenarios&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/entra/agent-id/configure-third-party-agents#integration-patterns-for-third-party-agents" rel="noopener noreferrer"&gt;third-party Agent ID integration patterns&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Second, the SPIFFE ID maps cleanly into the FIC subject. The workload identity &lt;code&gt;spiffe://poc.local/agent/identity-1&lt;/code&gt; remains visible at the trust boundary instead of being translated into an unrelated shared secret.&lt;/p&gt;

&lt;p&gt;Third, the public issuer surface is as important as the token. Federation succeeds only when the JWT &lt;code&gt;iss&lt;/code&gt;, FIC issuer, discovery document &lt;code&gt;issuer&lt;/code&gt;, discovery URL, JWKS location, active &lt;code&gt;kid&lt;/code&gt;, and RS256 signature all describe the same cryptographic authority. Microsoft Entra retrieves the external keys through that OIDC issuer contract before accepting the assertion (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation#how-it-works" rel="noopener noreferrer"&gt;workload identity federation mechanics&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Fourth, the identity that calls the downstream API is still the child agent identity. SPIRE authenticates the workload to the blueprint. It does not bypass Agent ID's parent-child token flow, consent, or downstream authorization model (&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-identities" rel="noopener noreferrer"&gt;agent identities&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  What the PoC deliberately does not prove
&lt;/h2&gt;

&lt;p&gt;The repository is unusually clear about its shortcuts, and that clarity matters.&lt;/p&gt;

&lt;p&gt;SPIRE state is ephemeral. Every Container App start deletes the server and agent data, regenerating the CA material, JWT signing key, and JWT &lt;code&gt;kid&lt;/code&gt;. Microsoft Entra can retain previously retrieved issuer metadata and keys, so exchanges can fail after a restart until its view refreshes (&lt;a href="https://github.com/astaykov/agentid-spiffe#spiffe-jwt-svid-to-entra-agent-id-poc" rel="noopener noreferrer"&gt;repository warning&lt;/a&gt;). This is the loudest reason not to mistake the sample for a production SPIRE topology.&lt;/p&gt;

&lt;p&gt;The agent uses a join token and &lt;code&gt;insecure_bootstrap = true&lt;/code&gt;, while the server and agent are co-located with the workload. Those choices remove infrastructure that would distract from the exchange, but they are explicitly marked as PoC-only in the &lt;a href="https://github.com/astaykov/agentid-spiffe/blob/main/src/spire/agent.conf" rel="noopener noreferrer"&gt;agent configuration&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The backend also logs the full JWT-SVID to simplify troubleshooting while the FIC is being wired up (&lt;a href="https://github.com/astaykov/agentid-spiffe/blob/main/src/backend/main.py" rel="noopener noreferrer"&gt;logging code&lt;/a&gt;). Do not carry that behavior into production. A JWT-SVID is a bearer credential susceptible to replay during its validity window, so it must not enter application logs, traces, prompts, or tool output (&lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md#71-replay-protection" rel="noopener noreferrer"&gt;JWT-SVID replay considerations&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;None of those shortcuts invalidate the demonstrated identity exchange. They define its boundary. The sample proves that a correctly issued JWT-SVID, signed with RS256 and backed by public OIDC discovery, can authenticate an Agent ID blueprint. It does not prove that one ephemeral container is how an organization should operate SPIRE.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trust contract is now executable
&lt;/h2&gt;

&lt;p&gt;The previous article ended with two control planes and one narrow trust contract. The PoC turns that sentence into running code.&lt;/p&gt;

&lt;p&gt;SPIRE attests the FastAPI process and issues &lt;code&gt;spiffe://poc.local/agent/identity-1&lt;/code&gt; a short-lived JWT-SVID. The OIDC discovery endpoint makes the active RSA public key available at a predictable public URL. The federated identity credential authorizes one exact issuer, SPIFFE ID, and audience to authenticate the blueprint. &lt;code&gt;fmi_path&lt;/code&gt; narrows the resulting T1 token to one child agent identity. That child obtains the final resource token under its own authorization boundary.&lt;/p&gt;

&lt;p&gt;No shared client secret crosses the container boundary. No X.509 certificate is copied into the agent. No custom token validator is added to Entra. The bridge uses the SPIFFE JWT identity format, standard OIDC discovery, Entra workload identity federation, and Agent ID's documented blueprint exchange (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;workload identity federation&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow" rel="noopener noreferrer"&gt;Agent ID token acquisition&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The issuer configuration is the hinge. It must be exact, public, standards-shaped, and cryptographically aligned with the JWT in flight. Get that right, and the SPIFFE workload can enter Entra as a governed agent identity without ever owning a long-lived Entra credential.&lt;/p&gt;

&lt;p&gt;That is the result this PoC was built to isolate.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/astaykov/agentid-spiffe" rel="noopener noreferrer"&gt;SPIFFE JWT-SVID to Entra Agent ID PoC&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/astaykov/your-spiffe-workload-can-authenticate-as-an-entra-agent-id-but-mind-the-trust-gap-3969"&gt;Your SPIFFE workload can authenticate as an Entra Agent ID, but mind the trust gap&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/astaykov/your-agentic-platform-doesnt-need-a-single-secret-managed-identities-workload-identity-3602"&gt;Your agentic platform doesn't need a single secret&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spiffe.io/docs/latest/spiffe-about/spiffe-concepts/" rel="noopener noreferrer"&gt;SPIFFE concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spiffe.io/docs/latest/spire-about/spire-concepts/" rel="noopener noreferrer"&gt;SPIRE concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spiffe.io/docs/latest/deploying/registering/" rel="noopener noreferrer"&gt;Registering workloads&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md" rel="noopener noreferrer"&gt;JWT-SVID specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openid.net/specs/openid-connect-discovery-1_0.html" rel="noopener noreferrer"&gt;OpenID Connect Discovery 1.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;Workload identity federation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Overview of federated identity credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust" rel="noopener noreferrer"&gt;Configure an application to trust an external identity provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations" rel="noopener noreferrer"&gt;Important considerations for federated identity credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/configure-third-party-agents" rel="noopener noreferrer"&gt;Integrate third-party agents with Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow" rel="noopener noreferrer"&gt;Authenticate and acquire tokens for autonomous agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-on-behalf-of-oauth-flow" rel="noopener noreferrer"&gt;Agent OAuth on-behalf-of flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-identities" rel="noopener noreferrer"&gt;Agent identities in Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-client-creds-grant-flow#third-case-access-token-request-with-a-federated-credential" rel="noopener noreferrer"&gt;OAuth 2.0 client credentials flow with a federated credential&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/python/api/msal/msal.application.confidentialclientapplication" rel="noopener noreferrer"&gt;MSAL Python ConfidentialClientApplication&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>entraagentid</category>
      <category>spiffe</category>
      <category>workloadidentity</category>
      <category>identitysecurity</category>
    </item>
    <item>
      <title>Your SPIFFE workload can authenticate as an Entra Agent ID, but mind the trust gap</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 04 Aug 2026 09:13:00 +0000</pubDate>
      <link>https://dev.to/astaykov/your-spiffe-workload-can-authenticate-as-an-entra-agent-id-but-mind-the-trust-gap-3969</link>
      <guid>https://dev.to/astaykov/your-spiffe-workload-can-authenticate-as-an-entra-agent-id-but-mind-the-trust-gap-3969</guid>
      <description>&lt;p&gt;SPIFFE with Microsoft Entra Agent ID sounds almost too tidy.&lt;/p&gt;

&lt;p&gt;Let SPIRE attest an AI agent running in Kubernetes, on-premises, or in another cloud. Give that workload a short-lived SPIFFE identity. Present the resulting token to Microsoft Entra. Receive an access token for a governed Entra Agent ID. No client secret, no certificate copied into a container, no cloud-specific identity shim.&lt;/p&gt;

&lt;p&gt;The architecture is viable. Microsoft explicitly lists &lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation#supported-scenarios" rel="noopener noreferrer"&gt;SPIFFE and SPIRE among its supported workload identity federation scenarios&lt;/a&gt;, and Entra Agent ID supports &lt;a href="https://learn.microsoft.com/entra/agent-id/configure-third-party-agents#use-workload-identity-federation-direct-identity-exchange" rel="noopener noreferrer"&gt;third-party agents through direct workload identity federation&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;But the clean diagram hides a trust gap.&lt;/p&gt;

&lt;p&gt;SPIFFE proves &lt;em&gt;which workload is running&lt;/em&gt;. Microsoft Entra decides &lt;em&gt;which agent identity that workload may act as&lt;/em&gt;. A federated identity credential connects those two statements. If that mapping, its issuer surface, or its audience design is wrong, a short-lived token merely helps the wrong workload become the wrong agent faster.&lt;/p&gt;

&lt;p&gt;That is the pattern worth understanding. Not "SPIFFE replaces Entra" and not "Entra understands SPIFFE natively." The real design has two identity control planes joined by one exact, case-sensitive trust contract.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;SPIFFE workload plane                     Microsoft Entra control plane

+---------------------------+            +-----------------------------+
| AI agent workload         |            | Agent identity blueprint    |
|                           |            |                             |
| spiffe://example.com/     |            | FIC matches:                |
| agent/invoice-reconciler  |            |   iss + sub + aud           |
+-------------+-------------+            +--------------+--------------+
              |                                         |
              | 1. Attestation                          |
              v                                         |
+-------------+-------------+                           |
| SPIRE Server + Agent      |                           |
|                           |                           |
| Issues short-lived        |                           |
| JWT-SVID                  |                           |
+-------------+-------------+                           |
              |                                         |
              | 2. JWT-SVID + fmi_path                  |
              +----------------------------------------&amp;gt;|
                                                        |
                                          3. Returns T1 |
                                                        v
                                         +--------------+--------------+
                                         | Child agent identity        |
                                         |                             |
                                         | T1 exchanged for a          |
                                         | resource-scoped token       |
                                         +--------------+--------------+
                                                        |
              4. Entra access token                     |
              &amp;lt;-----------------------------------------+

The SPIFFE workload authenticates the blueprint.
The child agent identity is the principal authorized on downstream APIs.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  SPIFFE and Entra answer different questions
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://spiffe.io/docs/latest/spiffe-about/overview/" rel="noopener noreferrer"&gt;SPIFFE&lt;/a&gt; defines a platform-neutral identity framework for software workloads. Its central identifier is the SPIFFE ID, a URI such as &lt;code&gt;spiffe://example.com/agent/invoice-reconciler&lt;/code&gt;. A SPIFFE implementation such as SPIRE attests a workload using runtime evidence, then makes short-lived identity documents available through the &lt;a href="https://spiffe.io/docs/latest/deploying/svids/" rel="noopener noreferrer"&gt;SPIFFE Workload API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Microsoft Entra Agent ID starts one layer higher. An &lt;a href="https://learn.microsoft.com/entra/agent-id/agent-identities" rel="noopener noreferrer"&gt;agent identity is a specialized service principal&lt;/a&gt; used to request tokens, receive tokens, and act either autonomously or in a user's context. The agent identity does not hold its own credential. Its &lt;a href="https://learn.microsoft.com/entra/agent-id/agent-identities#authorizing-agent-identities" rel="noopener noreferrer"&gt;agent identity blueprint holds credentials and acquires tokens on behalf of child agent identities&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;That division is useful:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SPIRE answers: &lt;em&gt;is this the workload that my runtime policy registered?&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;Entra answers: &lt;em&gt;which enterprise agent identity may this authenticated workload use, and what may that identity access?&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The distinction matters because workload authentication is not downstream authorization. A valid SPIFFE identity does not grant Microsoft Graph permissions, an Azure role, or consent to an API scope. It only supplies evidence that Entra can accept as a credential for the blueprint. The resulting agent identity remains the principal that needs authorization on the target resource, a separation Microsoft also makes explicit for &lt;a href="https://learn.microsoft.com/entra/agent-id/agent-identities" rel="noopener noreferrer"&gt;agent identity token acquisition&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Think of SPIRE as the passport office and Entra as the border authority. One establishes that the document belongs to a particular workload. The other decides which governed identity crosses the border and what that identity may do after entry.&lt;/p&gt;

&lt;h2&gt;
  
  
  Only one of the two SVID forms fits the exchange
&lt;/h2&gt;

&lt;p&gt;SPIFFE defines two common forms of SPIFFE Verifiable Identity Document, and they are not interchangeable.&lt;/p&gt;

&lt;p&gt;An &lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md" rel="noopener noreferrer"&gt;X.509-SVID&lt;/a&gt; is an X.509 certificate in which the workload's SPIFFE ID appears as the single URI Subject Alternative Name. It is a strong fit for workload-to-workload mutual TLS. The workload proves possession of the corresponding private key while both sides validate certificate chains against SPIFFE trust bundles.&lt;/p&gt;

&lt;p&gt;A &lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md" rel="noopener noreferrer"&gt;JWT-SVID&lt;/a&gt; is a signed bearer JWT. Its &lt;code&gt;sub&lt;/code&gt; claim must be the workload's SPIFFE ID, and it must contain both &lt;code&gt;aud&lt;/code&gt; and &lt;code&gt;exp&lt;/code&gt;. That shape fits Microsoft Entra workload identity federation because Entra's client credentials flow accepts a JWT from an external identity provider as the &lt;code&gt;client_assertion&lt;/code&gt; (&lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-client-creds-grant-flow#third-case-access-token-request-with-a-federated-credential" rel="noopener noreferrer"&gt;federated client credential flow&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is the first catch: &lt;em&gt;an X.509-SVID is not a federated identity assertion for this flow&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;You cannot hand Entra an X.509-SVID and expect it to extract the URI SAN, validate the SPIFFE certificate chain, and perform the workload identity exchange. Microsoft documents the external credential as a JWT assertion with an issuer, subject, and audience that match a configured federated identity credential (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation#how-it-works" rel="noopener noreferrer"&gt;workload identity federation mechanics&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Keep X.509-SVIDs for mTLS inside the workload plane. Mint a dedicated JWT-SVID for the Entra token exchange. Trying to make one credential format do both jobs weakens the design rather than simplifying it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bridge is an exact three-claim contract
&lt;/h2&gt;

&lt;p&gt;A federated identity credential, or FIC, tells Entra which external JWT may authenticate a workload identity. Three values form the contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;issuer&lt;/code&gt; must match the incoming JWT's &lt;code&gt;iss&lt;/code&gt; claim.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;subject&lt;/code&gt; must match the incoming JWT's &lt;code&gt;sub&lt;/code&gt; claim.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;audiences&lt;/code&gt; must contain the audience Entra expects in the incoming JWT, normally &lt;code&gt;api://AzureADTokenExchange&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microsoft performs this match &lt;a href="https://learn.microsoft.com/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0#how-do-federated-identity-credentials-work" rel="noopener noreferrer"&gt;exactly and case-sensitively&lt;/a&gt;. Wildcards are not supported in standard FIC property values, and an application or agent identity blueprint can have at most 20 FICs (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations#general-federated-identity-credential-considerations" rel="noopener noreferrer"&gt;FIC considerations&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For one production agent workload, the incoming JWT-SVID could look like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"iss"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://oidc.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"sub"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe://example.com/agent/invoice-reconciler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"aud"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;"api://AzureADTokenExchange"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"exp"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;1784304000&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The corresponding FIC on the agent identity blueprint's application object is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"name"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe-production-invoice-agent"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"issuer"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"https://oidc.example.com"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"subject"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"spiffe://example.com/agent/invoice-reconciler"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"audiences"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"api://AzureADTokenExchange"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;





&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Incoming JWT-SVID                         Federated identity credential

iss: https://oidc.example.com       ===&amp;gt; issuer: https://oidc.example.com

sub: spiffe://example.com/          ===&amp;gt; subject: spiffe://example.com/
  agent/invoice-reconciler                    agent/invoice-reconciler

aud: api://AzureADTokenExchange     ===&amp;gt; audiences:
                 api://AzureADTokenExchange

      EXACT, CASE-SENSITIVE MATCH

This mapping authenticates the blueprint. It does not grant downstream
permissions or carry a user's delegated authorization context.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;subject&lt;/code&gt; is where SPIFFE fits naturally. The &lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md#31-subject" rel="noopener noreferrer"&gt;JWT-SVID specification requires &lt;code&gt;sub&lt;/code&gt; to equal the workload's SPIFFE ID&lt;/a&gt;, so the FIC binds one precise SPIFFE workload identity to one blueprint credential path.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;audience&lt;/code&gt; is where architecture becomes policy. SPIFFE allows site-specific audiences and recommends scoping them to the intended recipient (&lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md#32-audience" rel="noopener noreferrer"&gt;JWT-SVID audience requirements&lt;/a&gt;). For this token, the intended recipient is Entra's token exchange. The workload should therefore request a JWT-SVID with the single audience &lt;code&gt;api://AzureADTokenExchange&lt;/code&gt;, matching the FIC.&lt;/p&gt;

&lt;p&gt;Do not reuse a JWT-SVID minted for an internal API. Do not add Entra as a second audience to a token already used elsewhere. A recipient of a multi-audience bearer token can replay that token to another listed audience, which is why the SPIFFE specification &lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md#72-audience" rel="noopener noreferrer"&gt;strongly recommends single-audience JWT-SVIDs&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  The token flow, without the hand-waving
&lt;/h2&gt;

&lt;p&gt;The autonomous flow has seven steps:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;SPIRE attests the agent workload according to its registration and runtime selectors.&lt;/li&gt;
&lt;li&gt;The workload requests a JWT-SVID from the local Workload API with &lt;code&gt;api://AzureADTokenExchange&lt;/code&gt; as its sole audience.&lt;/li&gt;
&lt;li&gt;SPIRE issues a short-lived JWT-SVID whose &lt;code&gt;sub&lt;/code&gt; is the workload's SPIFFE ID.&lt;/li&gt;
&lt;li&gt;The workload sends that JWT to the Microsoft identity platform token endpoint as &lt;code&gt;client_assertion&lt;/code&gt;, requests &lt;code&gt;api://AzureADTokenExchange/.default&lt;/code&gt;, and supplies the child agent identity's client ID in &lt;code&gt;fmi_path&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Entra discovers the external issuer's signing keys, validates the JWT, compares &lt;code&gt;iss&lt;/code&gt;, &lt;code&gt;sub&lt;/code&gt;, and &lt;code&gt;aud&lt;/code&gt; against the FIC on the blueprint, and returns the blueprint exchange token &lt;code&gt;T1&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;The workload sends &lt;code&gt;T1&lt;/code&gt; as the &lt;code&gt;client_assertion&lt;/code&gt; in a second request whose &lt;code&gt;client_id&lt;/code&gt; is the child agent identity and whose scope names the downstream resource.&lt;/li&gt;
&lt;li&gt;Entra validates the blueprint-to-child relationship and issues a resource-scoped access token in which the agent identity, not the SPIFFE workload, is the enterprise principal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The first request uses the JWT-SVID to authenticate the blueprint. The &lt;code&gt;fmi_path&lt;/code&gt; parameter selects the child agent identity for which the blueprint is requesting an exchange token, exactly as documented in the &lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow#request-a-token-for-the-agent-identity-blueprint" rel="noopener noreferrer"&gt;autonomous agent authentication flow&lt;/a&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={agent-identity-blueprint-client-id}
&amp;amp;scope=api%3A%2F%2FAzureADTokenExchange%2F.default
&amp;amp;fmi_path={agent-identity-client-id}
&amp;amp;client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&amp;amp;client_assertion={jwt-svid}
&amp;amp;grant_type=client_credentials
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That request returns &lt;code&gt;T1&lt;/code&gt;, not the Microsoft Graph token. The second request switches &lt;code&gt;client_id&lt;/code&gt; from the blueprint to the child agent identity and uses &lt;code&gt;T1&lt;/code&gt; as its assertion (&lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow#request-an-agent-identity-token" rel="noopener noreferrer"&gt;request an agent identity token&lt;/a&gt;):&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={agent-identity-client-id}
&amp;amp;scope=https%3A%2F%2Fgraph.microsoft.com%2F.default
&amp;amp;client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&amp;amp;client_assertion={agent-blueprint-token-T1}
&amp;amp;grant_type=client_credentials
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The blueprint authenticates with the external assertion, but the child agent identity becomes the principal in the final resource token. That distinction is not administrative trivia. If the downstream API authorizes the blueprint rather than the agent identity, the architecture has collapsed the attribution and least-privilege boundary it adopted Agent ID to create.&lt;/p&gt;

&lt;h2&gt;
  
  
  A SPIFFE bundle endpoint is not OIDC discovery
&lt;/h2&gt;

&lt;p&gt;This is the catch most diagrams omit.&lt;/p&gt;

&lt;p&gt;SPIFFE defines how trust bundles carry X.509 authorities and JWT signing keys. Its &lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Federation.md" rel="noopener noreferrer"&gt;federation specification&lt;/a&gt; defines bundle endpoints through which one SPIFFE trust domain can retrieve another domain's current bundle. JWT-SVID signing keys appear in a SPIFFE bundle as JWK entries whose &lt;code&gt;use&lt;/code&gt; value is &lt;code&gt;jwt-svid&lt;/code&gt; (&lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md#6-representation-in-the-spiffe-bundle" rel="noopener noreferrer"&gt;JWT-SVID key publication&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Microsoft Entra expects something different. The configured FIC issuer must be a URL compatible with OIDC discovery, and Entra uses that issuer URL to retrieve the keys required to validate the external token (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust#other-identity-providers" rel="noopener noreferrer"&gt;external issuer configuration&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;A SPIFFE bundle endpoint and an OIDC discovery endpoint both publish trust material over HTTPS, but they are not the same protocol surface. Similar nouns do not create interoperability.&lt;/p&gt;

&lt;p&gt;The production architecture therefore needs an issuer service that:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exposes &lt;code&gt;/.well-known/openid-configuration&lt;/code&gt; for the configured HTTPS issuer;&lt;/li&gt;
&lt;li&gt;exposes a JWKS endpoint referenced by that discovery document;&lt;/li&gt;
&lt;li&gt;publishes the active SPIRE JWT signing keys in an Entra-compatible JWKS;&lt;/li&gt;
&lt;li&gt;keeps the JWT-SVID &lt;code&gt;iss&lt;/code&gt; claim exactly aligned with the configured issuer URL;&lt;/li&gt;
&lt;li&gt;rotates published keys without creating a window in which valid JWT-SVIDs cannot be verified.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That service is part of your identity control plane. Treating it as a static JSON file on an incidental web server is the identity equivalent of putting a production certificate chain in a shared folder and calling it PKI.&lt;/p&gt;

&lt;p&gt;Microsoft also documents that the identity platform stores only the first 100 signing keys downloaded from an external OIDC endpoint (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation#how-it-works" rel="noopener noreferrer"&gt;workload identity federation mechanics&lt;/a&gt;). That limit should be easy to respect, but it makes stale-key accumulation an operational concern rather than harmless clutter.&lt;/p&gt;

&lt;h2&gt;
  
  
  Short-lived bearer tokens can still be replayed
&lt;/h2&gt;

&lt;p&gt;JWT-SVIDs reduce credential lifetime. They do not eliminate bearer-token behavior.&lt;/p&gt;

&lt;p&gt;The SPIFFE specification is direct: &lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md#71-replay-protection" rel="noopener noreferrer"&gt;JWT-SVIDs are susceptible to replay&lt;/a&gt;. A short &lt;code&gt;exp&lt;/code&gt; narrows the window. A &lt;code&gt;jti&lt;/code&gt; can support replay detection, but JWT-SVID validators are not required to track &lt;code&gt;jti&lt;/code&gt; uniqueness. Confidential transport remains mandatory because possession of the token is sufficient to present it during its valid lifetime (&lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md#73-transport-security" rel="noopener noreferrer"&gt;JWT-SVID transport security&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For this exchange, that produces a clear baseline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;request the JWT-SVID only when an Entra token is needed;&lt;/li&gt;
&lt;li&gt;use one audience, &lt;code&gt;api://AzureADTokenExchange&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;keep JWT-SVID lifetime as short as the exchange path tolerates;&lt;/li&gt;
&lt;li&gt;never log the assertion or place it in agent prompts, tool output, or distributed traces;&lt;/li&gt;
&lt;li&gt;send it directly to Entra over TLS;&lt;/li&gt;
&lt;li&gt;cache the resulting resource token according to its lifetime, not the source assertion.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Short-lived credentials change the incident from "rotate a secret that may have escaped months ago" to "contain a replay window measured in minutes." That is a major improvement. It is not immunity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Rotation now crosses an organizational boundary
&lt;/h2&gt;

&lt;p&gt;SPIRE owns workload attestation and JWT signing-key rotation. The team operating the OIDC facade owns discovery availability and JWKS publication. Microsoft Entra owns external-token validation and FIC matching. The Agent ID team owns blueprint and child identity boundaries.&lt;/p&gt;

&lt;p&gt;That is four operational responsibilities, even if one platform team implements all of them.&lt;/p&gt;

&lt;p&gt;Microsoft currently documents RS256 as the supported signing algorithm for workload identity federation token exchange, while noting that other algorithms may work but are not tested (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations#supported-signing-algorithms-and-issuers" rel="noopener noreferrer"&gt;supported signing algorithms&lt;/a&gt;). An architect should therefore require the SPIRE JWT signing profile exposed to Entra to use RS256 rather than treating JOSE algorithm choice as an implementation detail.&lt;/p&gt;

&lt;p&gt;FIC changes also take time to propagate. Microsoft warns that token requests made shortly after a FIC change can fail with &lt;code&gt;AADSTS70021&lt;/code&gt; until regional caches update, and recommends retry logic (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations#time-for-federated-credential-changes-to-propagate" rel="noopener noreferrer"&gt;FIC propagation&lt;/a&gt;). Key rollover and FIC rollout need staged tests, overlap, monitoring, and a rollback path. "The tokens are short-lived" does not make control-plane changes atomic.&lt;/p&gt;

&lt;h2&gt;
  
  
  The 20-credential limit is an architecture signal
&lt;/h2&gt;

&lt;p&gt;Standard FICs bind exact issuer and subject values, and each application or blueprint is limited to 20 credentials (&lt;a href="https://learn.microsoft.com/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0#design-considerations" rel="noopener noreferrer"&gt;FIC design considerations&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That limit should stop an enterprise from attaching every SPIFFE workload in a fleet to one blueprint. It reinforces the boundary model from [[Enterprise-architect-decision-framework-for-Entra-Agent-ID-blueprints-on-non-Microsoft-platforms]]: one blueprint should represent a deliberate trust and blast-radius boundary, not become a universal token vending machine.&lt;/p&gt;

&lt;p&gt;Microsoft has introduced &lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identities-flexible-federated-identity-credentials" rel="noopener noreferrer"&gt;flexible federated identity credentials&lt;/a&gt; that can match subject patterns and selected custom claims, but the feature is currently preview. Pattern matching also changes the risk model. An exact SPIFFE ID binds one registered workload to the blueprint. A claims expression binds a class of workloads. That can reduce object count, but it increases the importance of SPIRE registration policy and namespace governance.&lt;/p&gt;

&lt;p&gt;Scale is not permission to blur identity boundaries. If 20 exact bindings are insufficient, first ask whether the blueprint boundary is too broad. Only then consider a flexible expression.&lt;/p&gt;

&lt;h2&gt;
  
  
  Workload federation authenticates the client in both autonomous and OBO flows
&lt;/h2&gt;

&lt;p&gt;The source assertion proves the workload's identity. That makes the JWT-SVID a &lt;em&gt;client credential&lt;/em&gt;, not a statement about whether the resulting flow is autonomous or delegated.&lt;/p&gt;

&lt;p&gt;Microsoft separates autonomous agent authorization from on-behalf-of authorization, but both flows still require the client to authenticate. In the autonomous flow, the SPIFFE-backed FIC authenticates the blueprint before the child agent identity requests a resource token with its own application permissions. In an OBO flow, the same client-authentication path can be combined with an incoming user assertion so that both agent and user context participate (&lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow" rel="noopener noreferrer"&gt;Microsoft identity platform OBO flow&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/entra/agent-id/agent-on-behalf-of-oauth-flow" rel="noopener noreferrer"&gt;Agent ID OBO flow&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For Agent ID, this remains a two-stage exchange. First, the blueprint presents the JWT-SVID as its &lt;code&gt;client_assertion&lt;/code&gt;, includes the child agent identity in &lt;code&gt;fmi_path&lt;/code&gt;, and receives &lt;code&gt;T1&lt;/code&gt;. The child agent identity then performs the OBO exchange by presenting both &lt;code&gt;T1&lt;/code&gt; as its client credential and the incoming user access token as the user assertion:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id={agent-identity-client-id}
&amp;amp;scope=https%3A%2F%2Fresource.example.com%2Fscope1
&amp;amp;client_assertion_type=urn%3Aietf%3Aparams%3Aoauth%3Aclient-assertion-type%3Ajwt-bearer
&amp;amp;client_assertion={agent-blueprint-token-T1}
&amp;amp;grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Ajwt-bearer
&amp;amp;assertion={incoming-user-access-token}
&amp;amp;requested_token_use=on_behalf_of
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The distinction between &lt;code&gt;client_assertion&lt;/code&gt; and &lt;code&gt;assertion&lt;/code&gt; is the architecture. &lt;code&gt;client_assertion&lt;/code&gt; proves which client is making the exchange. &lt;code&gt;assertion&lt;/code&gt; carries the signed-in user's delegated context. Workload identity federation replaces the client's stored secret; it does not replace the user token, user consent, delegated permissions, or OBO protocol.&lt;/p&gt;

&lt;p&gt;This also sharpens the connection to [[The-industry-does-not-have-an-agent-identity-propagation-standard-yet]]. SPIFFE can authenticate the workload at a cross-platform boundary, and Entra can combine that client authentication with an Entra-issued user assertion. SPIFFE still does not independently preserve a human subject across identity provider domains, capture consent, or define who is accountable for translating user context.&lt;/p&gt;

&lt;p&gt;Do not put a user's identifier into a private JWT-SVID claim and call the problem solved. The JWT-SVID authenticates the client. The incoming access token and OBO exchange carry delegated authorization.&lt;/p&gt;

&lt;h2&gt;
  
  
  The production decision
&lt;/h2&gt;

&lt;p&gt;Use this pattern when all of the following are true:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the organization already operates SPIRE as a workload identity authority;&lt;/li&gt;
&lt;li&gt;the agent runs outside Azure or across heterogeneous infrastructure;&lt;/li&gt;
&lt;li&gt;the agent needs a governed Microsoft Entra identity for autonomous or OBO access;&lt;/li&gt;
&lt;li&gt;eliminating blueprint secrets is worth operating an OIDC discovery and JWKS publication surface;&lt;/li&gt;
&lt;li&gt;SPIFFE registration policy and Entra blueprint ownership have accountable operators;&lt;/li&gt;
&lt;li&gt;exact workload-to-blueprint mappings fit the intended blast-radius model.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Do not use it as a shortcut around delegated consent or OBO. Use the JWT-SVID to authenticate the client, then supply the incoming user token through the documented &lt;code&gt;assertion&lt;/code&gt; parameter. Reject the pattern when your only available credential is an X.509-SVID or when nobody owns the issuer surface and key-rotation path.&lt;/p&gt;

&lt;p&gt;The final architecture is not "SPIFFE authenticates to Entra." That phrase hides too much.&lt;/p&gt;

&lt;p&gt;SPIRE attests a workload. A dedicated JWT-SVID presents that workload identity to a deliberately operated OIDC issuer surface. An exact FIC authorizes that issuer, SPIFFE ID, and audience to authenticate an agent identity blueprint. Entra then issues a resource token for the governed agent identity, whose permissions remain independently controlled.&lt;/p&gt;

&lt;p&gt;Two control planes. One narrow trust contract. No shared secret.&lt;/p&gt;

&lt;p&gt;That is a strong architecture, provided you operate all three parts of the sentence.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://spiffe.io/docs/latest/spiffe-about/overview/" rel="noopener noreferrer"&gt;SPIFFE overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spiffe.io/docs/latest/deploying/svids/" rel="noopener noreferrer"&gt;Working with SVIDs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/JWT-SVID.md" rel="noopener noreferrer"&gt;JWT-SVID specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/X509-SVID.md" rel="noopener noreferrer"&gt;X.509-SVID specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/spiffe/spiffe/blob/main/standards/SPIFFE_Federation.md" rel="noopener noreferrer"&gt;SPIFFE federation specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;Microsoft Entra workload identity federation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations" rel="noopener noreferrer"&gt;Important considerations for federated identity credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust" rel="noopener noreferrer"&gt;Configure an app to trust an external identity provider&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/configure-third-party-agents" rel="noopener noreferrer"&gt;Integrate third-party agents with Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-identities" rel="noopener noreferrer"&gt;Agent identities in Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow" rel="noopener noreferrer"&gt;Authenticate and acquire tokens for autonomous agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow" rel="noopener noreferrer"&gt;Microsoft identity platform and OAuth 2.0 On-Behalf-Of flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-on-behalf-of-oauth-flow" rel="noopener noreferrer"&gt;Agent OAuth flows: On-behalf-of flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Federated identity credentials overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-client-creds-grant-flow#third-case-access-token-request-with-a-federated-credential" rel="noopener noreferrer"&gt;OAuth 2.0 client credentials flow with a federated credential&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identities-flexible-federated-identity-credentials" rel="noopener noreferrer"&gt;Flexible federated identity credentials&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7523" rel="noopener noreferrer"&gt;RFC 7523: JWT profile for OAuth 2.0 client authentication and authorization grants&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>entraagentid</category>
      <category>spiffe</category>
      <category>workloadidentity</category>
      <category>identitysecurity</category>
    </item>
    <item>
      <title>Microsoft Graph least privilege has two control planes</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 28 Jul 2026 09:37:00 +0000</pubDate>
      <link>https://dev.to/astaykov/microsoft-graph-least-privilege-has-two-control-planes-4hnf</link>
      <guid>https://dev.to/astaykov/microsoft-graph-least-privilege-has-two-control-planes-4hnf</guid>
      <description>&lt;p&gt;The Microsoft Graph least-privilege story has improved a lot. That is not the problem.&lt;/p&gt;

&lt;p&gt;The problem is that many teams are still looking at only half of it.&lt;/p&gt;

&lt;p&gt;Granular permissions such as &lt;code&gt;User.Create&lt;/code&gt;, &lt;code&gt;User.EnableDisableAccount.All&lt;/code&gt;, &lt;code&gt;User.RevokeSessions.All&lt;/code&gt;, and &lt;code&gt;Application.ReadWrite.OwnedBy&lt;/code&gt; are a better design surface than the old habit of reaching for &lt;code&gt;Directory.ReadWrite.All&lt;/code&gt; whenever an app needed to do anything useful in the directory. Microsoft Graph permissions intentionally follow a &lt;code&gt;{resource}.{operation}.{constraint}&lt;/code&gt; naming pattern, which lets engineers express the operation an app is allowed to perform instead of throwing the app into a tenant-wide bucket (&lt;a href="https://learn.microsoft.com/graph/permissions-overview#permissions-naming-pattern" rel="noopener noreferrer"&gt;Microsoft Graph permissions overview&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That shift matters. But it does not finish the authorization model.&lt;/p&gt;

&lt;p&gt;The less understood truth is this: Microsoft Graph least privilege has &lt;em&gt;two control planes&lt;/em&gt;. The first is the Graph permission plane: what the client application is allowed to request from Microsoft Graph. The second is the Microsoft Entra control plane: who or what is allowed to control the application object, the service principal, and the directory objects the app will act on.&lt;/p&gt;

&lt;p&gt;If you design only the first plane, your architecture can still be wildly overprivileged.&lt;/p&gt;

&lt;h2&gt;
  
  
  The first plane: Graph permissions authorize the client
&lt;/h2&gt;

&lt;p&gt;Start with the piece engineers usually know.&lt;/p&gt;

&lt;p&gt;Microsoft Graph supports delegated access and app-only access. In delegated access, the app calls Microsoft Graph on behalf of a signed-in user. In app-only access, the app calls Microsoft Graph as itself, without a signed-in user (&lt;a href="https://learn.microsoft.com/graph/permissions-overview#permission-types" rel="noopener noreferrer"&gt;Microsoft Graph permissions overview&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Delegated permissions are the easiest place to see the intersection model. Microsoft states it plainly: in a delegated scenario, the privileges an app has are determined by the Microsoft Graph permissions granted to the app &lt;em&gt;and&lt;/em&gt; the user's own permissions (&lt;a href="https://learn.microsoft.com/graph/permissions-overview#delegated-permissions" rel="noopener noreferrer"&gt;Microsoft Graph delegated permissions&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That sentence should be printed on every onboarding app design review.&lt;/p&gt;

&lt;p&gt;If your custom HR onboarding app has been granted the &lt;em&gt;delegated&lt;/em&gt; permission &lt;code&gt;User.Create&lt;/code&gt;, the app has permission to call the create-user operation. But the signed-in user still needs the Entra authority to create users. The Graph permission authorizes the client. The Entra role authorizes the human. Delegated Microsoft Graph access needs both.&lt;/p&gt;

&lt;p&gt;Flip it around and the same lesson holds. If the signed-in user is a User Administrator, but the app has not been granted the right delegated Microsoft Graph permission, the user's admin role does not magically donate authority to the client. A user can perform an operation in the Entra admin center and still fail through a custom app because the app itself is not authorized for the Graph call.&lt;/p&gt;

&lt;p&gt;This is the first correction:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;In a delegated scenario, admin role assignment is not a substitute for Graph consent, and Graph consent is not a substitute for admin role assignment.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  The second plane: Entra controls who can control the thing doing the calling
&lt;/h2&gt;

&lt;p&gt;Now comes the part that gets missed.&lt;/p&gt;

&lt;p&gt;Microsoft Entra applications are represented by two related objects: the application object and the service principal object. The application object is the global template for the app. The service principal is the local representation of that app in a specific tenant, and it defines what the app can actually do in that tenant, who can access it, and what resources it can access (&lt;a href="https://learn.microsoft.com/entra/identity-platform/app-objects-and-service-principals" rel="noopener noreferrer"&gt;Application and service principal objects in Microsoft Entra ID&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That distinction is not directory trivia. It is a control-plane boundary.&lt;/p&gt;

&lt;p&gt;The application object controls things like redirect URIs, credentials, &lt;em&gt;requested&lt;/em&gt; permissions, exposed APIs, and ownership. The service principal controls the enterprise application instance in the tenant: assignments, local policies, &lt;em&gt;consented&lt;/em&gt; permissions, provisioning configuration, sign-in behavior, and other tenant-specific settings (&lt;a href="https://learn.microsoft.com/entra/identity-platform/app-objects-and-service-principals#service-principal-object" rel="noopener noreferrer"&gt;Application and service principal objects&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;So when you ask, "is this Graph app least privileged?" the permission list is not enough. You also need to ask &lt;em&gt;who can mutate&lt;/em&gt; the app registration, &lt;em&gt;who can mutate the&lt;/em&gt; enterprise application, who can add credentials, who can add owners, who can change user assignment, and who can change the permissions the app requests.&lt;/p&gt;

&lt;p&gt;That is the second plane.&lt;/p&gt;

&lt;h2&gt;
  
  
  Owner is not a label, owner is control
&lt;/h2&gt;

&lt;p&gt;Application and enterprise application (service principal) ownership is one of the most underestimated privilege paths in Microsoft Entra.&lt;/p&gt;

&lt;p&gt;Microsoft documents that an owner of an enterprise application can manage the organization-specific configuration of the application, including single sign-on, provisioning, and user assignment. The owner can also add or remove other owners. More importantly, Microsoft states that owners have the same permissions as application administrators, scoped to an individual application (&lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/overview-assign-app-owners" rel="noopener noreferrer"&gt;Enterprise application ownership&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Read that again slowly: &lt;em&gt;application administrator, scoped to one application&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That is not harmless metadata. &lt;strong&gt;That is delegated control over a highly sensitive object&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;The default user permissions documentation is even more concrete:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Owners of application registrations can update application authentication (reply urls), &lt;strong&gt;credentials&lt;/strong&gt;, owners, permissions, policies, and basic properties. Owners of enterprise applications can update service principal &lt;strong&gt;credentials&lt;/strong&gt;, owners, permissions, &lt;strong&gt;app role assignments&lt;/strong&gt;, provisioning settings, and related policies (&lt;a href="https://learn.microsoft.com/entra/fundamentals/users-default-permissions#ownership-permissions" rel="noopener noreferrer"&gt;Default user permissions and ownership permissions&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Microsoft also calls out the dangerous consequence directly: &lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;... the application might have more permissions than the owner, which can become elevation of privilege because an owner can create or update objects while impersonating the application (&lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/overview-assign-app-owners" rel="noopener noreferrer"&gt;Enterprise application ownership note&lt;/a&gt;).&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the part many app teams miss. The owner does not need to have the same directory role as the app. If the app has powerful application permissions, the person who can add a credential or change ownership can potentially create a path to act as the app.&lt;/p&gt;

&lt;p&gt;Static secrets get most of the attention here, but ownership is the quieter root. If I can become owner, add myself as another owner, add or rotate credentials, adjust the app configuration, or influence the service principal's local settings, I am no longer merely adjacent to the app. I am in the control plane of the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bad pattern: broad Graph permission plus casual ownership
&lt;/h2&gt;

&lt;p&gt;Here is the pattern I still see too often.&lt;/p&gt;

&lt;p&gt;An engineering team builds a Graph automation app. They correctly avoid &lt;code&gt;Directory.ReadWrite.All&lt;/code&gt; and use a narrower Graph permission. Good. Then they leave three developers as owners of the app registration, two operations engineers as owners of the enterprise application, and a shared service account with broad rights because "someone needs to maintain it."&lt;/p&gt;

&lt;p&gt;On paper, the app permission set looks defensible. In reality, the control plane around the app is loose.&lt;/p&gt;

&lt;p&gt;That is not least privilege. That is narrow API permission sitting inside broad object control.&lt;/p&gt;

&lt;p&gt;The better question is not only "what Graph permissions does the app have?" The better question is: "who can change the identity that has those permissions?"&lt;/p&gt;

&lt;p&gt;If the answer is a long list of permanent owners, the app is overprivileged even if the Graph permission list looks elegant.&lt;/p&gt;

&lt;h2&gt;
  
  
  Scoped Entra roles are the missing design tool
&lt;/h2&gt;

&lt;p&gt;This is where Microsoft Entra role scoping becomes much more interesting than most app teams realize.&lt;/p&gt;

&lt;p&gt;Microsoft Entra roles are usually assigned at tenant scope, but Microsoft also supports assigning roles at narrower scopes, including application registrations and administrative units. Microsoft describes the resource a role assignment applies to as the scope, and supports restricted scope for built-in and custom roles (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/manage-roles-portal" rel="noopener noreferrer"&gt;Assign Microsoft Entra roles&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For app registrations, the model is direct. Instead of granting someone tenant-wide Application Administrator, you can assign a relevant built-in or even custom role scoped to a single app registration. Microsoft describes this as a way to let a principal update credentials and basic properties of a single app without granting tenant-wide application administration (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/manage-roles-portal#assign-roles-with-app-registration-scope" rel="noopener noreferrer"&gt;Assign roles with app registration scope&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The Graph shape is simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments
Content-Type: application/json

{
  "@odata.type": "#microsoft.graph.unifiedRoleAssignment",
  "principalId": "&amp;lt;principal-object-id&amp;gt;",
  "roleDefinitionId": "&amp;lt;role-definition-id&amp;gt;",
  "directoryScopeId": "/&amp;lt;application-object-id&amp;gt;"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That &lt;code&gt;directoryScopeId&lt;/code&gt; value matters. For app registration scope, &lt;code&gt;/&amp;lt;id&amp;gt;&lt;/code&gt; means the principal can manage that Microsoft Entra object. Microsoft explicitly contrasts that with &lt;code&gt;/administrativeUnits/&amp;lt;id&amp;gt;&lt;/code&gt;, which means the principal can manage the members of the administrative unit based on the assigned role (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/manage-roles-portal#assign-roles-with-app-registration-scope" rel="noopener noreferrer"&gt;Assign roles with app registration scope&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That distinction is the whole architecture in miniature. Same role-assignment resource. Different scope semantics. Different blast radius.&lt;/p&gt;

&lt;h2&gt;
  
  
  Administrative units are not just for humans
&lt;/h2&gt;

&lt;p&gt;Administrative units are usually explained as a delegation feature for helpdesk and regional administration. That is true, but incomplete.&lt;/p&gt;

&lt;p&gt;Microsoft documents that Entra roles can be assigned with administrative-unit scope so the role permissions apply only when managing members of that administrative unit, not tenant-wide settings or unrelated directory objects (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/manage-roles-portal#assign-roles-with-administrative-unit-scope" rel="noopener noreferrer"&gt;Assign roles with administrative unit scope&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For example, a User Administrator scoped to the "Germany Employees" administrative unit can manage users in that administrative unit without becoming User Administrator for the whole tenant, subject to the role's supported administrative-unit behavior and restrictions (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/manage-roles-portal#assign-roles-with-administrative-unit-scope" rel="noopener noreferrer"&gt;Administrative-unit role scope&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That already matters for humans. It matters even more for automation.&lt;/p&gt;

&lt;p&gt;If you are building an onboarding app for five subsidiaries, why should the app's runtime identity be able to manage every user in the tenant? If the work is limited to five administrative units, the role assignment should be limited to those administrative units. &lt;/p&gt;

&lt;p&gt;This is the balance shift: Graph permissions are always tenant-wide. Entra role assignments can be scoped.&lt;/p&gt;

&lt;h2&gt;
  
  
  The widely unknown pattern: service principal plus administrative-unit scope
&lt;/h2&gt;

&lt;p&gt;Now for the part that is still too obscure.&lt;/p&gt;

&lt;p&gt;You can assign a Microsoft Entra role to a service principal at administrative-unit scope.&lt;/p&gt;

&lt;p&gt;This is not how most portal walkthroughs teach the feature. Most examples show a user as the principal. Some examples mention groups. The architecture pattern for app identities is still widely unknown. But the Graph contract supports the pieces.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;unifiedRoleAssignment&lt;/code&gt; resource represents a role definition assigned to a principal at a scope, and Microsoft lists supported principals as users, role-assignable groups, and service principals (&lt;a href="https://learn.microsoft.com/graph/api/resources/unifiedroleassignment?view=graph-rest-1.0" rel="noopener noreferrer"&gt;unifiedRoleAssignment resource type&lt;/a&gt;). The create-role-assignment API documents &lt;code&gt;directoryScopeId&lt;/code&gt; formats for the directory provider, including &lt;code&gt;/&lt;/code&gt; for tenant-wide scope, &lt;code&gt;/administrativeUnits/{administrativeunit-id}&lt;/code&gt; for administrative-unit scope, and &lt;code&gt;/{application-objectId}&lt;/code&gt; for application scope (&lt;a href="https://learn.microsoft.com/graph/api/rbacapplication-post-roleassignments?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Create unifiedRoleAssignment&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Put those two facts together and the payload becomes obvious:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight http"&gt;&lt;code&gt;&lt;span class="err"&gt;POST https://graph.microsoft.com/v1.0/roleManagement/directory/roleAssignments
Content-Type: application/json

{
  "@odata.type": "#microsoft.graph.unifiedRoleAssignment",
  "principalId": "&amp;lt;service-principal-object-id&amp;gt;",
  "roleDefinitionId": "&amp;lt;role-definition-id&amp;gt;",
  "directoryScopeId": "/administrativeUnits/&amp;lt;administrative-unit-id&amp;gt;"
}
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That assignment says: this service principal has this Entra role, but only for the members of this administrative unit, subject to the role's administrative-unit support.&lt;/p&gt;

&lt;p&gt;In practice, there is one more wrinkle engineers need to know. Microsoft documents that service principals and guest users do not receive directory read permissions by default, and service principals using an administrative-unit-scoped role assignment need corresponding &lt;em&gt;read&lt;/em&gt; permissions, commonly Directory Readers at tenant scope or another role that includes the necessary read permissions (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/manage-roles-portal#assign-roles-with-administrative-unit-scope" rel="noopener noreferrer"&gt;Service principals and administrative-unit scoped role assignments&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That tenant-scope read dependency annoys people at first. It should not. Read capability and write authority are different risks. A service principal might need enough tenant-level read to resolve directory objects while its actual administrative authority remains scoped to one administrative unit. That is still a materially better design than granting a tenant-wide write role because the app needs to manage one slice of the directory.&lt;/p&gt;

&lt;p&gt;The pattern is not theoretical. It is validated, battle-tested, and still underused.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this changes Graph automation design
&lt;/h2&gt;

&lt;p&gt;Once you see the two planes, the design review changes.&lt;/p&gt;

&lt;p&gt;For a delegated app, you ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Microsoft Graph delegated permission does the client need?&lt;/li&gt;
&lt;li&gt;Which Entra role must the signed-in user hold?&lt;/li&gt;
&lt;li&gt;Can that role be scoped to an administrative unit or application registration instead of the tenant?&lt;/li&gt;
&lt;li&gt;Who owns the app registration and enterprise application?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For an app-only automation identity, you ask:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What Microsoft Graph application permission does the service principal need?&lt;/li&gt;
&lt;li&gt;Can the operation be represented by an Entra role assignment instead of a broad Graph application permission?&lt;/li&gt;
&lt;li&gt;Can that Entra role be scoped to an administrative unit or specific (application) object?&lt;/li&gt;
&lt;li&gt;Does the service principal have only the directory read permissions needed to make that scoped role usable?&lt;/li&gt;
&lt;li&gt;Who can add credentials or owners to the app and service principal?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That last question is not an afterthought. It is often the difference between least privilege and theater.&lt;/p&gt;

&lt;h2&gt;
  
  
  A practical model for app teams
&lt;/h2&gt;

&lt;p&gt;Here is the model I would use with app and platform engineers.&lt;/p&gt;

&lt;p&gt;First, choose the narrowest Microsoft Graph permission that matches the API operation. Use &lt;code&gt;User.Create&lt;/code&gt; if the app creates users. Use &lt;code&gt;Application.ReadWrite.OwnedBy&lt;/code&gt; if the app only needs to manage applications it owns. Avoid directory-wide permissions unless the operation truly has no narrower resource-specific permission (&lt;a href="https://learn.microsoft.com/graph/permissions-reference" rel="noopener noreferrer"&gt;Microsoft Graph permissions reference&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Second, decide whether the app needs delegated or app-only access. In delegated mode, remember that Graph evaluates the client permission &lt;strong&gt;and&lt;/strong&gt; the user's own authority. In app-only mode, remember that the app is the actor, so application permissions &lt;strong&gt;or role assignments&lt;/strong&gt; must be treated as production privilege (&lt;a href="https://learn.microsoft.com/graph/permissions-overview#permission-types" rel="noopener noreferrer"&gt;Microsoft Graph permission types&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Third, scope Entra roles as aggressively as the business process allows. If the operator manages one app, use app-registration scope. If the automation manages one population, use administrative-unit scope. If the app really needs tenant-wide scope, make that an explicit exception, not the default (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/manage-roles-portal" rel="noopener noreferrer"&gt;Assign Microsoft Entra roles at different scopes&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Fourth, treat owners as privileged. Application owners and enterprise application owners should be reviewed like role assignments, because they can control the object that holds the app's permissions and credentials (&lt;a href="https://learn.microsoft.com/entra/fundamentals/users-default-permissions#ownership-permissions" rel="noopener noreferrer"&gt;Default user permissions and ownership permissions&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Finally, keep the object model visible. The app registration and the service principal are not interchangeable. The app registration is the template. The service principal is the tenant-local instance that controls what the app can actually do in that tenant (&lt;a href="https://learn.microsoft.com/entra/identity-platform/app-objects-and-service-principals#relationship-between-application-objects-and-service-principals" rel="noopener noreferrer"&gt;Application and service principal relationship&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The sentence to bring to your next design review
&lt;/h2&gt;

&lt;p&gt;Least privilege for Microsoft Graph is not "we picked a smaller Graph permission."&lt;/p&gt;

&lt;p&gt;That is a good start. It is not the finish line.&lt;/p&gt;

&lt;p&gt;Least privilege for Microsoft Graph means the client has the narrowest Graph permission, the actor has the narrowest Entra authority, the role assignment has the narrowest scope, and the app object has the narrowest ownership model.&lt;/p&gt;

&lt;p&gt;Miss any one of those, and your beautiful permission list can still hide a control-plane problem.&lt;/p&gt;

&lt;p&gt;The industry is finally getting better Microsoft Graph permission granularity. Now app teams need to get equally serious about the Entra control plane around those permissions.&lt;/p&gt;

&lt;p&gt;Because the app that can touch your directory is only as least-privileged as the people and identities that can control the app.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/permissions-overview" rel="noopener noreferrer"&gt;Overview of Microsoft Graph permissions&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/permissions-reference" rel="noopener noreferrer"&gt;Microsoft Graph permissions reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/app-objects-and-service-principals" rel="noopener noreferrer"&gt;Application and service principal objects in Microsoft Entra ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/overview-assign-app-owners" rel="noopener noreferrer"&gt;Overview of enterprise application ownership in Microsoft Entra ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/fundamentals/users-default-permissions#object-ownership" rel="noopener noreferrer"&gt;What are the default user permissions in Microsoft Entra ID?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/manage-roles-portal" rel="noopener noreferrer"&gt;Assign Microsoft Entra roles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/api/resources/unifiedroleassignment?view=graph-rest-1.0" rel="noopener noreferrer"&gt;unifiedRoleAssignment resource type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/api/rbacapplication-post-roleassignments?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Create unifiedRoleAssignment&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>microsoftgraph</category>
      <category>microsoftentra</category>
      <category>leastprivilege</category>
      <category>appsecurity</category>
    </item>
    <item>
      <title>Enterprise architects: your overdue Entra decision is an agent CSA schema</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Thu, 23 Jul 2026 09:23:00 +0000</pubDate>
      <link>https://dev.to/astaykov/enterprise-architects-your-overdue-entra-decision-is-an-agent-csa-schema-2j5n</link>
      <guid>https://dev.to/astaykov/enterprise-architects-your-overdue-entra-decision-is-an-agent-csa-schema-2j5n</guid>
      <description>&lt;p&gt;If you are an enterprise architect working on Microsoft Entra and AI agents, your first overdue job is not another policy wizard, another dashboard, or another governance steering committee. It is schema design.&lt;/p&gt;

&lt;p&gt;Specifically, it is deciding how you classify non-human identities with &lt;a href="https://learn.microsoft.com/entra/fundamentals/custom-security-attributes-overview" rel="noopener noreferrer"&gt;custom security attributes in Microsoft Entra&lt;/a&gt;. Not eventually. Up front.&lt;/p&gt;

&lt;p&gt;I keep seeing the same pattern across customers of every size: teams move quickly on agent experimentation, they onboard identities, they test controls, and then they realize they have no consistent attribute language for policy scope. At that point, every policy becomes a naming convention problem in disguise.&lt;/p&gt;

&lt;p&gt;That is backwards.&lt;/p&gt;

&lt;h2&gt;
  
  
  The control plane starts with classification
&lt;/h2&gt;

&lt;p&gt;Custom security attributes are not decorative metadata. They are tenant-scoped key-value classifications you can assign to users, enterprise applications (service principals), and agent identities that are modeled as a service principal subtype, with dedicated role and permission boundaries for who can define and assign them (&lt;a href="https://learn.microsoft.com/entra/fundamentals/custom-security-attributes-overview" rel="noopener noreferrer"&gt;overview&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/custom-security-attributes-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Graph model&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/entra/agent-id/agent-service-principals" rel="noopener noreferrer"&gt;agent identity service principal model&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That alone should change how architects think about them. This is not "nice to have taxonomy." This is policy input.&lt;/p&gt;

&lt;p&gt;Microsoft Entra Conditional Access for agents supports attribute-driven targeting with custom security attributes, and policy evaluation happens during token issuance and refresh, not just at policy authoring time (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/agent-id#attribute-driven-conditional-access" rel="noopener noreferrer"&gt;Conditional Access for agents&lt;/a&gt;). In other words: if your classification is sloppy, your runtime decisions are sloppy.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why agents raise the stakes
&lt;/h2&gt;

&lt;p&gt;You can say "an agent identity is still a service principal" and be technically correct. Microsoft Entra Agent ID is built on service principal infrastructure (&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-service-principals" rel="noopener noreferrer"&gt;agent identities, service principals, and applications&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;You can also miss the point.&lt;/p&gt;

&lt;p&gt;Agent identity introduces a blueprint-centered model where one blueprint can represent many agents, where blueprint-level policy decisions can affect all derived identities, and where operational accountability (owners and sponsors) is part of the model (&lt;a href="https://learn.microsoft.com/entra/agent-id/key-concepts" rel="noopener noreferrer"&gt;key concepts&lt;/a&gt;). This is not how most architects historically modeled "traditional" workload identities.&lt;/p&gt;

&lt;p&gt;So yes, same substrate. Different operating semantics.&lt;/p&gt;

&lt;p&gt;That difference is exactly why CSA strategy for agents should be deliberate, not inherited by accident from old workload identity templates.&lt;/p&gt;

&lt;h2&gt;
  
  
  Reuse your existing workload schema or create a dedicated agent schema?
&lt;/h2&gt;

&lt;p&gt;This is the question every architecture board eventually reaches.&lt;/p&gt;

&lt;p&gt;The honest answer is conditional, not ideological.&lt;/p&gt;

&lt;p&gt;Reusing your existing workload schema has real advantages. It accelerates rollout, avoids duplicate governance machinery, and can be enough if your workload identity estate is already cleanly governed and your agent population is still limited.&lt;/p&gt;

&lt;p&gt;But here is the trade-off many teams underestimate: semantic collision.&lt;/p&gt;

&lt;p&gt;When the same attribute family has to describe static daemon services, integration apps, and AI agents with interactive and autonomous behavior patterns, naming starts to drift. Policy intent gets fuzzy. People read "criticality" or "environment" and assume shared meaning where meaning has already diverged. Then policy scopes become misleading even when the syntax is valid.&lt;/p&gt;

&lt;p&gt;That is the failure mode I care about most. Not policy syntax errors. Policy &lt;em&gt;intent&lt;/em&gt; errors.&lt;/p&gt;

&lt;p&gt;For regulated enterprises, or any organization expecting meaningful agent scale, my default recommendation is to establish a dedicated agent CSA schema and keep crosswalk mappings to legacy workload attributes where needed. You can still align governance. You just stop pretending the populations are identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architect's mistake: tactical granularity too early
&lt;/h2&gt;

&lt;p&gt;Most teams that finally commit to an agent schema make the same second mistake: they overengineer it.&lt;/p&gt;

&lt;p&gt;They try to encode every nuanced runtime trait into attributes on day one. They turn schema design into a cataloging exercise. They create dozens of values nobody will govern consistently.&lt;/p&gt;

&lt;p&gt;Do not do that.&lt;/p&gt;

&lt;p&gt;Strategic schema design means defining &lt;em&gt;long-lived control boundaries&lt;/em&gt;, not short-lived implementation detail. Microsoft documents clear limits and lifecycle constraints on custom security attributes, including tenant-level definition caps and immutable naming characteristics, which is exactly why restraint is a design virtue (&lt;a href="https://learn.microsoft.com/entra/fundamentals/custom-security-attributes-overview#limits-and-constraints" rel="noopener noreferrer"&gt;limits and constraints&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/custom-security-attributes-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Graph overview&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Start with coarse boundaries that support high-impact policy outcomes you know you will still care about two years from now.&lt;/p&gt;

&lt;h2&gt;
  
  
  Two coarse boundaries worth defining first
&lt;/h2&gt;

&lt;p&gt;First boundary: approved workload state.&lt;/p&gt;

&lt;p&gt;If your operating model says "no AI agent should sign in until explicitly approved," then encode exactly that as a first-class classification and enforce from there. This is the kind of architectural control objective that survives platform changes.&lt;/p&gt;

&lt;p&gt;Second boundary: department trust segmentation.&lt;/p&gt;

&lt;p&gt;If your operating model says "financial department agents should only authenticate within finance-defined trust boundaries," then encode that boundary at classification level before you layer finer controls. Entra policy and filtering surfaces are strongest when your classes reflect organizational trust zones, not implementation trivia (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/agent-id#attribute-driven-conditional-access" rel="noopener noreferrer"&gt;attribute-driven Conditional Access for agents&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/agent-id/policy-autonomous-agents#allow-only-specific-agents-to-access-resources" rel="noopener noreferrer"&gt;autonomous agent policy walkthrough&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;These are strategic guardrails. The fine-grained boundaries should come from additional controls and deeper policy patterns, which we can cover in follow-up pieces.&lt;/p&gt;

&lt;h2&gt;
  
  
  Governance is part of schema, not a separate phase
&lt;/h2&gt;

&lt;p&gt;Custom security attributes are security-sensitive by design, and Entra uses dedicated attribute roles for definition and assignment. Even Global Administrator is not automatically enough for these operations by default (&lt;a href="https://learn.microsoft.com/entra/fundamentals/custom-security-attributes-overview#custom-security-attribute-roles" rel="noopener noreferrer"&gt;custom security attribute roles&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#attribute-assignment-administrator" rel="noopener noreferrer"&gt;permissions reference&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Treat that as a feature, not friction.&lt;/p&gt;

&lt;p&gt;Your schema decision should include explicit ownership for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;who defines agent attribute sets and values,&lt;/li&gt;
&lt;li&gt;who is allowed to assign and update values on agent identities,&lt;/li&gt;
&lt;li&gt;how schema changes are reviewed when agent programs evolve.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If those answers are not defined with the schema, your taxonomy will drift faster than your policies can keep up.&lt;/p&gt;

&lt;h2&gt;
  
  
  The practical position
&lt;/h2&gt;

&lt;p&gt;Could you reuse your current workload identity schema for AI agents? Yes.&lt;/p&gt;

&lt;p&gt;Should you, by default, in a large regulated enterprise? Usually no.&lt;/p&gt;

&lt;p&gt;The better default is a dedicated agent schema with a small number of coarse, policy-driving attributes. Make the boundaries obvious. Keep the vocabulary stable. Optimize for long-term policy clarity, not short-term convenience.&lt;/p&gt;

&lt;p&gt;This is architecture work in the most literal sense: defining the shape of control before control logic gets complicated.&lt;/p&gt;

&lt;p&gt;We already did this in this series when discussing dynamic consent accumulation and agent governance pressure in &lt;a href="https://dev.to/astaykov/the-overlooked-gem-in-microsoft-entra-that-gives-your-ai-agents-super-powers-3mde"&gt;The Overlooked Gem in Microsoft Entra&lt;/a&gt; and &lt;a href="https://dev.to/astaykov/your-agent-is-becoming-the-crown-jewel-soc-reviews-and-governance-for-the-dynamic-consent-era-23l3"&gt;Your Agent Is Becoming the Crown Jewel&lt;/a&gt;. This article just names the next prerequisite: the classification layer that keeps your controls honest.&lt;/p&gt;

&lt;p&gt;If you delay this decision, every downstream guardrail becomes harder to reason about.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/fundamentals/custom-security-attributes-overview" rel="noopener noreferrer"&gt;What are custom security attributes in Microsoft Entra ID?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/custom-security-attributes-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Overview of custom security attributes using Microsoft Graph API&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/custom-security-attributes-apps" rel="noopener noreferrer"&gt;Manage custom security attributes for an application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/agent-id#attribute-driven-conditional-access" rel="noopener noreferrer"&gt;Conditional Access for agents (attribute-driven Conditional Access)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/agent-id/policy-autonomous-agents#allow-only-specific-agents-to-access-resources" rel="noopener noreferrer"&gt;Conditional Access for autonomous agents (allow only specific agents to access resources)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/key-concepts" rel="noopener noreferrer"&gt;Microsoft Entra Agent ID key concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-service-principals" rel="noopener noreferrer"&gt;Agent identities, service principals, and applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#attribute-assignment-administrator" rel="noopener noreferrer"&gt;Role permissions reference: Attribute Assignment Administrator&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>entraagentid</category>
      <category>customsecurityattributes</category>
      <category>aiagents</category>
      <category>zerotrust</category>
    </item>
    <item>
      <title>How enterprise architects should decide Entra Agent ID blueprint boundaries for non-Microsoft platforms</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 21 Jul 2026 10:47:00 +0000</pubDate>
      <link>https://dev.to/astaykov/how-enterprise-architects-should-decide-entra-agent-id-blueprint-boundaries-for-non-microsoft-oaa</link>
      <guid>https://dev.to/astaykov/how-enterprise-architects-should-decide-entra-agent-id-blueprint-boundaries-for-non-microsoft-oaa</guid>
      <description>&lt;p&gt;Enterprise architects keep asking me the same question: &lt;em&gt;how many agent identity blueprints should we create&lt;/em&gt; for third-party or custom agent platforms connected to Microsoft Entra Agent ID?&lt;/p&gt;

&lt;p&gt;That question sounds operational, but it is architectural. You are deciding failure containment, ownership boundaries, and governance behavior, all at once.&lt;/p&gt;

&lt;p&gt;Before we go deeper, one important acknowledgement. If your agents are built in Microsoft-managed builder paths such as Copilot Studio and Agent 365 integration flows, identity provisioning is largely handled for you, including automatic Agent ID creation and shared blueprint behavior in that platform path (&lt;a href="https://learn.microsoft.com/en-us/microsoft-copilot-studio/admin-use-entra-agent-identities" rel="noopener noreferrer"&gt;Copilot Studio automatic Agent IDs&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/microsoft-agent-365/builder/identity" rel="noopener noreferrer"&gt;Agent 365 identity integration&lt;/a&gt;). If you choose that platform, you also accept its blueprint identity model by design.&lt;/p&gt;

&lt;p&gt;This article is for the other world: non-Microsoft-built agent platforms, where your enterprise architecture team owns the blueprint boundary decisions.&lt;/p&gt;

&lt;p&gt;I am also not going to summarize the canonical guidance. You should read both &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/how-to-plan-agent-identity-architecture" rel="noopener noreferrer"&gt;Plan your agent identity architecture&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/concept-agent-id-design-patterns" rel="noopener noreferrer"&gt;Microsoft Entra Agent ID design patterns&lt;/a&gt; directly, then come back. This piece is about decision framing, trade-offs, and policy posture.&lt;/p&gt;

&lt;h2&gt;
  
  
  Stop asking for a count, start defining trust boundaries
&lt;/h2&gt;

&lt;p&gt;When architects ask "how many blueprints," they usually mean one of three fears:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;"I do not want one compromised component to become tenant-wide damage."&lt;/li&gt;
&lt;li&gt;"I do not want central identity teams to become a ticket bottleneck."&lt;/li&gt;
&lt;li&gt;"I do not want unrecoverable sprawl six months from now."&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;That is a trust-boundary problem, not an object-count problem.&lt;/p&gt;

&lt;p&gt;Microsoft's own architecture guidance is explicit that blueprint count should follow trust boundaries, while many common concerns (replicas, memory partitioning, session context) are not valid reasons to split identities (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/how-to-plan-agent-identity-architecture" rel="noopener noreferrer"&gt;planning guidance&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/concept-agent-id-design-patterns" rel="noopener noreferrer"&gt;design patterns&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If your team still starts with org chart lines like "one blueprint per project by default," you are optimizing for slide readability, not for incident containment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The blueprint power model, and the part teams keep confusing
&lt;/h2&gt;

&lt;p&gt;You need one clear mental model.&lt;/p&gt;

&lt;p&gt;The blueprint is your identity factory and authentication anchor. It is what enables creation of child agent identities at scale, including runtime or ephemeral patterns when your architecture needs on-demand identities (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-id-creation-channels" rel="noopener noreferrer"&gt;creation channels&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That is good news for delivery teams. Developers do not need to raise a central ticket for every single new identity if the right blueprint channel and controls are already in place.&lt;/p&gt;

&lt;p&gt;But do not confuse that with automatic governance.&lt;/p&gt;

&lt;p&gt;Blueprints are not where your organization magically enforces business access policy. Consent boundaries, entitlement policy, Conditional Access, and app-governance controls are separate control planes that still require explicit enterprise policy definition and rollout (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview" rel="noopener noreferrer"&gt;user/admin consent model&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/identity-governance-applications-define" rel="noopener noreferrer"&gt;define governance policy&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Treat the blueprint as a creation authority, not as the permission-grant layer and not as your per-agent lifecycle governance switch.&lt;/p&gt;

&lt;p&gt;In plain terms, Entra Agent ID and Agent 365 capabilities do not enforce your internal architecture doctrine by themselves. Without internal policies, teams will improvise.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blast radius reality: one compromised agent can become many identities
&lt;/h2&gt;

&lt;p&gt;Here is the uncomfortable part.&lt;/p&gt;

&lt;p&gt;Within a shared blueprint trust boundary, if one agent runtime is compromised and the attacker can use the same blueprint credential path, that attacker can request tokens that act as sibling identities under that blueprint boundary. This is exactly why Microsoft frames blueprint compromise as potentially affecting all child identities in that blueprint scope (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/how-to-plan-agent-identity-architecture" rel="noopener noreferrer"&gt;planning guidance&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That is the architectural reason you model by trust boundary first.&lt;/p&gt;

&lt;p&gt;It also explains a frequent false comfort pattern: "we will just split certificates or managed identity details but keep one shared blueprint boundary." If the effective authentication authority is still the same blueprint trust surface, your containment gain is smaller than people assume.&lt;/p&gt;

&lt;h2&gt;
  
  
  A risk-first decision framework for blueprint boundaries
&lt;/h2&gt;

&lt;p&gt;Use this sequence. Do not skip steps.&lt;/p&gt;

&lt;h3&gt;
  
  
  1) Define hard trust boundaries
&lt;/h3&gt;

&lt;p&gt;Map runtime, secret stores, network segmentation, and operational ownership. If compromise should not cross between two zones, they should not share a blueprint.&lt;/p&gt;

&lt;h3&gt;
  
  
  2) Define failure containment targets
&lt;/h3&gt;

&lt;p&gt;State explicitly what should be kill-switch scope:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;one identity,&lt;/li&gt;
&lt;li&gt;one blueprint,&lt;/li&gt;
&lt;li&gt;one environment,&lt;/li&gt;
&lt;li&gt;or tenant-wide.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If this is undefined, your incident response design is fiction.&lt;/p&gt;

&lt;h3&gt;
  
  
  3) Define permission topology
&lt;/h3&gt;

&lt;p&gt;Decide what is inheritable baseline and what is per-agent specialization. Keep inherited baseline tight and role-specific differentiation local to agent identities.&lt;/p&gt;

&lt;h3&gt;
  
  
  4) Define lifecycle behavior
&lt;/h3&gt;

&lt;p&gt;Will identities be long-lived, or created dynamically for specific task windows? If dynamic creation is a first-class requirement, blueprint channel design and guardrails become even more critical.&lt;/p&gt;

&lt;h3&gt;
  
  
  5) Define governance ownership
&lt;/h3&gt;

&lt;p&gt;Who approves what, who can create via which channel, who can override in emergencies, who attests quarterly. If there is no owner matrix, there is no governance.&lt;/p&gt;

&lt;h2&gt;
  
  
  When one blueprint is right, and when it is reckless
&lt;/h2&gt;

&lt;p&gt;One blueprint with multiple agent identities is often correct when teams share the same trust boundary, runtime envelope, and operational ownership, while still needing per-agent attribution and differentiated downstream access (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/concept-agent-id-design-patterns" rel="noopener noreferrer"&gt;design patterns&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Concrete example: if your agents run in the same container orchestration boundary (for example, a shared Kubernetes namespace on AKS, EKS, or GKE), that is usually a strong indicator for one shared blueprint in that boundary. In practice, namespace-level controls define who can deploy, who can mutate workloads, what network policies apply, and what mounted/shared resources are reachable.&lt;/p&gt;

&lt;p&gt;That matters because your real security perimeter is often the namespace and its attached runtime controls, not an individual pod. Most engineering organizations can consistently enforce controls at namespace scope for developers and CI/CD pipelines, while per-pod isolation is harder to guarantee as a durable operating model.&lt;/p&gt;

&lt;p&gt;It becomes reckless when you cross security domains and still keep one blueprint for convenience. Separate teams, separate secret domains, separate runtime isolation, or separate regulatory boundaries should usually trigger separate blueprints.&lt;/p&gt;

&lt;h2&gt;
  
  
  Policy starter kit for enterprise architects
&lt;/h2&gt;

&lt;p&gt;If you want consistency across teams, publish a short internal policy before the next project starts.&lt;/p&gt;

&lt;h3&gt;
  
  
  Blueprint policy baseline
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;One blueprint per trust boundary unless exception approved.&lt;/li&gt;
&lt;li&gt;Mandatory sponsor and owner assignment, with periodic attestation.&lt;/li&gt;
&lt;li&gt;Naming convention that encodes environment, domain, and business owner.&lt;/li&gt;
&lt;li&gt;Conditional Access baseline and monitoring baseline applied at blueprint and identity levels where applicable (&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/best-practices-agent-id" rel="noopener noreferrer"&gt;best practices&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/manage-agent-identities-admin" rel="noopener noreferrer"&gt;manage and govern agent identities&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Creation channel policy
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Define which channels are allowed in your org: admin center, Graph, IaC/CLI, product-integrated channels.&lt;/li&gt;
&lt;li&gt;Bind each channel to approved roles and permission grants.&lt;/li&gt;
&lt;li&gt;Prohibit ad-hoc consent paths for production-grade identity creation unless explicitly reviewed (&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent" rel="noopener noreferrer"&gt;configure user consent&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Split and merge rubric
&lt;/h3&gt;

&lt;p&gt;Require architecture review on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;trust-boundary changes,&lt;/li&gt;
&lt;li&gt;new regulated data exposure,&lt;/li&gt;
&lt;li&gt;new external execution platform,&lt;/li&gt;
&lt;li&gt;incident lessons that show containment gaps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If teams cannot explain why two workloads can safely share compromise blast radius, they should not share a blueprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  The architect checklist to force before production
&lt;/h2&gt;

&lt;p&gt;Before approving blueprint design, require crisp answers to these questions:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;If one agent runtime is compromised, what exact identity scope can the attacker influence?&lt;/li&gt;
&lt;li&gt;Which trust boundaries are represented, and why are they safe to share?&lt;/li&gt;
&lt;li&gt;Which channels can create identities, and who controls them?&lt;/li&gt;
&lt;li&gt;Which policies prevent consent and permission sprawl?&lt;/li&gt;
&lt;li&gt;What is the fastest kill-switch path during incident response?&lt;/li&gt;
&lt;li&gt;What quarterly evidence proves this model is still valid?&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If your team cannot answer those six questions in one review, your blueprint model is not ready for production.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/concept-agent-id-design-patterns" rel="noopener noreferrer"&gt;Microsoft Entra Agent ID design patterns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/how-to-plan-agent-identity-architecture" rel="noopener noreferrer"&gt;Plan your agent identity architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/microsoft-copilot-studio/admin-use-entra-agent-identities" rel="noopener noreferrer"&gt;Automatically create Microsoft Entra Agent IDs for Copilot Studio agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/microsoft-agent-365/builder/identity" rel="noopener noreferrer"&gt;Agent identity integration for Copilot Studio&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-id-creation-channels" rel="noopener noreferrer"&gt;How are agent identities created?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/best-practices-agent-id" rel="noopener noreferrer"&gt;Best practices for Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview" rel="noopener noreferrer"&gt;Overview of user and admin consent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent" rel="noopener noreferrer"&gt;Configure how users consent to applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/identity-governance-applications-define" rel="noopener noreferrer"&gt;Define organizational policies for governing access to applications in your environment&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/identity-governance-applications-deploy" rel="noopener noreferrer"&gt;Deploying organizational policies for governing access to applications integrated with Microsoft Entra ID&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>entraagentid</category>
      <category>identitygovernance</category>
      <category>aiagents</category>
      <category>zerotrust</category>
    </item>
    <item>
      <title>The industry does not have an agent identity propagation standard yet</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 14 Jul 2026 11:07:00 +0000</pubDate>
      <link>https://dev.to/astaykov/the-industry-does-not-have-an-agent-identity-propagation-standard-yet-3fmf</link>
      <guid>https://dev.to/astaykov/the-industry-does-not-have-an-agent-identity-propagation-standard-yet-3fmf</guid>
      <description>&lt;p&gt;Most architecture conversations about AI agents still get stuck on the wrong boundary.&lt;/p&gt;

&lt;p&gt;Someone draws an agent in Azure, another agent in AWS, a resource behind Ping Identity or Auth0, a Microsoft Entra-protected API, an MCP server in the middle, and a user somewhere at the beginning of the chain. Then the room asks: can Microsoft Entra Agent ID work if the agent is not hosted on Microsoft infrastructure?&lt;/p&gt;

&lt;p&gt;That is the easy question. Hosting is not the boundary.&lt;/p&gt;

&lt;p&gt;Identity provider domain is the boundary.&lt;/p&gt;

&lt;p&gt;An application can live in AWS, Google Cloud, a co-location rack, or a basement server room and still use Microsoft Entra ID for sign-in. The same principle applies to agents. Microsoft explicitly positions Entra Agent ID as working with Microsoft and non-Microsoft platforms, including integrations through the Microsoft Entra Auth SDK sidecar and workload identity federation (&lt;a href="https://learn.microsoft.com/entra/agent-id/what-is-microsoft-entra-agent-id" rel="noopener noreferrer"&gt;Microsoft Entra Agent ID&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/entra/agent-id/configure-third-party-agents" rel="noopener noreferrer"&gt;third-party agent integration patterns&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The hard part starts when the flow leaves one identity provider and must preserve the original user context in another identity provider's world.&lt;/p&gt;

&lt;p&gt;That is where the industry is still awkward. Not because there are no standards. There are standards. The problem is that the standards describe token exchange mechanics better than they describe enterprise trust governance for long, agentic, multi-hop chains.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa8lh33gibedd8dmb6uou.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fa8lh33gibedd8dmb6uou.png" alt="A user starts in one identity provider domain, passes through agents, MCP servers, and resources, then crosses into another identity provider domain before returning to downstream APIs. The hard part is not hosting. The hard part is preserving trustworthy user context across identity provider boundaries." width="800" height="377"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The diagram above is the mental model. A user signs in, an application invokes an agent, the agent calls a tool or MCP server, another agent appears, and eventually a REST API or database needs to decide whether to trust the original user's context. Some arrows stay inside Microsoft Entra. Some arrows cross into Ping Identity or another provider. Some resources are custom APIs. Some are Microsoft resources. Some are agentic systems calling other agentic systems.&lt;/p&gt;

&lt;p&gt;The diagram looks like a flow. In practice, it is a chain of trust decisions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Federation is not token exchange
&lt;/h2&gt;

&lt;p&gt;One of the most useful audience questions in the discussion was simple: "We already solve multiple identity providers with federation. Why is this different for agents?"&lt;/p&gt;

&lt;p&gt;That question matters because it separates two things teams often blend together.&lt;/p&gt;

&lt;p&gt;Federation solves an interactive authentication problem. A user starts at one identity provider, is redirected to another identity provider, proves who they are, and returns with a token or assertion the application can use. That pattern is well understood in SAML, OpenID Connect, and enterprise SSO designs (&lt;a href="https://openid.net/specs/openid-connect-core-1_0.html" rel="noopener noreferrer"&gt;OpenID Connect Core&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-protocols-oidc" rel="noopener noreferrer"&gt;Microsoft identity platform and OpenID Connect&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Token exchange solves a different problem. A component that already received one token asks an authorization server for another token intended for a different resource or context. The standard that most directly describes this is &lt;a href="https://www.rfc-editor.org/rfc/rfc8693.html" rel="noopener noreferrer"&gt;OAuth 2.0 Token Exchange, RFC 8693&lt;/a&gt;. RFC 8693 defines a Security Token Service style protocol for requesting and obtaining tokens from OAuth authorization servers, including delegation and impersonation semantics.&lt;/p&gt;

&lt;p&gt;Those are not the same motion.&lt;/p&gt;

&lt;p&gt;Federation is the front door. Token exchange is what happens in the hallway after the front door has already closed.&lt;/p&gt;

&lt;p&gt;That distinction becomes brutally important for agents because agents create more hallways. A user does not simply sign in and call one API. The user asks an agent, the agent calls a tool, the tool calls an MCP server, the MCP server calls another agent, and that agent calls a REST API. Each hop may need a different token audience, a different scope, a different policy evaluation, and a different interpretation of who is acting.&lt;/p&gt;

&lt;h2&gt;
  
  
  RFC 8693 gives us language, not the whole operating model
&lt;/h2&gt;

&lt;p&gt;RFC 8693 is important because it gives the industry precise words for what keeps showing up in agent conversations: &lt;code&gt;subject_token&lt;/code&gt;, &lt;code&gt;actor_token&lt;/code&gt;, &lt;code&gt;audience&lt;/code&gt;, &lt;code&gt;resource&lt;/code&gt;, &lt;code&gt;scope&lt;/code&gt;, &lt;code&gt;act&lt;/code&gt;, and &lt;code&gt;may_act&lt;/code&gt; (&lt;a href="https://www.rfc-editor.org/rfc/rfc8693.html" rel="noopener noreferrer"&gt;RFC 8693&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;It also draws a clean line between impersonation and delegation. In impersonation, principal A becomes indistinguishable from principal B within the token's rights context. In delegation, principal A remains visible as the actor representing principal B (&lt;a href="https://www.rfc-editor.org/rfc/rfc8693.html#section-1.1" rel="noopener noreferrer"&gt;RFC 8693, delegation vs. impersonation&lt;/a&gt;). For agents, that distinction is not academic. An enterprise should almost never want an agent to become invisible as the human. It wants the user, the agent, and the delegation relationship to remain visible.&lt;/p&gt;

&lt;p&gt;RFC 8693 also defines the &lt;code&gt;act&lt;/code&gt; claim as a way to represent the current actor and even nested actor chains in a JWT (&lt;a href="https://www.rfc-editor.org/rfc/rfc8693.html#section-4.1" rel="noopener noreferrer"&gt;RFC 8693, actor claim&lt;/a&gt;). That sounds like exactly what we need.&lt;/p&gt;

&lt;p&gt;But the standard is careful about its own boundary. It defines a protocol. It does not solve every deployment trust model. It explicitly leaves token semantics, policy decisions, and many security characteristics to implementations and deployment configuration (&lt;a href="https://www.rfc-editor.org/rfc/rfc8693.html#section-1" rel="noopener noreferrer"&gt;RFC 8693 introduction&lt;/a&gt;, &lt;a href="https://www.rfc-editor.org/rfc/rfc8693.html#section-5" rel="noopener noreferrer"&gt;RFC 8693 security considerations&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That is the gap.&lt;/p&gt;

&lt;p&gt;The standard can describe a token exchange. It cannot, by itself, answer the enterprise question: who configured the mapping that allows an actor from identity provider A to arrive at identity provider B and claim a privileged user context?&lt;/p&gt;

&lt;p&gt;That question is not protocol syntax. That question is governance.&lt;/p&gt;

&lt;h2&gt;
  
  
  Autonomous agents are the solved part
&lt;/h2&gt;

&lt;p&gt;If there is no user context to preserve, the picture is much cleaner.&lt;/p&gt;

&lt;p&gt;An autonomous agent can authenticate as itself using client credentials, a certificate, a federated identity credential, or a managed identity, depending on the platform and operating model. Microsoft Entra Agent ID supports autonomous access and delegated access as different modes for agent identities (&lt;a href="https://learn.microsoft.com/entra/agent-id/what-are-agent-identities#what-agent-identities-enable" rel="noopener noreferrer"&gt;what agent identities enable&lt;/a&gt;). The Microsoft Entra Auth SDK sidecar is not the authentication method for the agent itself. It is a token-handling pattern that keeps credential exchange, OBO flows, token caching, and credential-source abstraction outside the agent code (&lt;a href="https://learn.microsoft.com/entra/agent-id/authentication-with-auth-sdk-sidecar" rel="noopener noreferrer"&gt;Microsoft Entra Auth SDK sidecar&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is also where workload identity federation fits well. Microsoft documents workload identity federation as a way for applications to access Microsoft Entra-protected resources without managing secrets, by trusting tokens from external identity providers (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;workload identity federation&lt;/a&gt;). Entra Agent ID's third-party integration guidance describes agents on external platforms exchanging native workload identity tokens for Microsoft Entra tokens before calling APIs (&lt;a href="https://learn.microsoft.com/entra/agent-id/configure-third-party-agents" rel="noopener noreferrer"&gt;integrate third-party agents&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This pattern is not limited to Microsoft. AWS, Google Cloud, Kubernetes ecosystems, and SPIFFE-based systems all lean into the same broad direction: give workloads short-lived, attestable identities instead of static secrets. SPIFFE describes itself as a set of open-source standards for identifying software systems in dynamic and heterogeneous environments, with short-lived identity documents called SVIDs delivered through a workload API (&lt;a href="https://spiffe.io/docs/latest/spiffe-about/overview/" rel="noopener noreferrer"&gt;SPIFFE overview&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;For fully autonomous agents, the industry has enough pieces to build sane systems today.&lt;/p&gt;

&lt;p&gt;The agent proves what workload it is. The target resource validates the token. The authorization system checks the agent's assigned rights. No human context needs to survive the journey.&lt;/p&gt;

&lt;p&gt;That is not trivial, but it is tractable.&lt;/p&gt;

&lt;h2&gt;
  
  
  User context is where the floor drops
&lt;/h2&gt;

&lt;p&gt;The difficult case is the one the audience kept circling back to: an employee signs in, an agent acts on behalf of that employee, the flow crosses identity providers, and a downstream resource must still know which human user started the journey.&lt;/p&gt;

&lt;p&gt;Microsoft Entra Agent ID supports on-behalf-of flows for agents. The documented OBO pattern uses a user token sent to the agent identity blueprint, then performs token exchanges so the resulting resource token carries the signed-in user's delegated context together with the agent identity (&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-on-behalf-of-oauth-flow" rel="noopener noreferrer"&gt;Agent OAuth flows: on behalf of flow&lt;/a&gt;). Microsoft recommends using approved SDKs such as Microsoft.Identity.Web or the Microsoft Entra Auth SDK sidecar because manual implementation is complex and error-prone (&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-on-behalf-of-oauth-flow" rel="noopener noreferrer"&gt;Agent OAuth flows: on behalf of flow&lt;/a&gt;, &lt;a href="https://learn.microsoft.com/entra/agent-id/authentication-with-auth-sdk-sidecar#sidecar-sample-scenarios" rel="noopener noreferrer"&gt;sidecar sample scenarios&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Inside one coherent identity domain, that is already a serious amount of machinery. Across identity providers, it becomes a trust governance problem.&lt;/p&gt;

&lt;p&gt;The downstream system is no longer asking only, "Is this token valid?" It is asking a longer set of questions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who was the original user?&lt;/li&gt;
&lt;li&gt;Which agent is acting now?&lt;/li&gt;
&lt;li&gt;Which prior agents or services participated in the chain?&lt;/li&gt;
&lt;li&gt;Which identity provider translated the context?&lt;/li&gt;
&lt;li&gt;Who configured that translation?&lt;/li&gt;
&lt;li&gt;Are we allowed to trust that mapping?&lt;/li&gt;
&lt;li&gt;How do we monitor and revoke that trust when risk changes?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is where the diagram matters. Six hops are not theoretical in an agentic architecture. They are what happens when a human asks one assistant to do work that uses an MCP server, which delegates to another specialized agent, which calls a vendor API, which then calls an internal API.&lt;/p&gt;

&lt;p&gt;The standards do not stop you from creating such a chain. OAuth OBO style flows do not impose a natural business limit on how many middle tiers can appear. RFC 8693 can represent actor chains through nested &lt;code&gt;act&lt;/code&gt; claims. In theory, the chain can keep growing (&lt;a href="https://www.rfc-editor.org/rfc/rfc8693.html#section-4.1" rel="noopener noreferrer"&gt;RFC 8693 actor claim&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Then the old boring transport layer walks into the room.&lt;/p&gt;

&lt;p&gt;Bearer tokens are commonly presented to resource servers in the HTTP &lt;code&gt;Authorization&lt;/code&gt; header field using &lt;code&gt;Authorization: Bearer ...&lt;/code&gt; (&lt;a href="https://www.rfc-editor.org/rfc/rfc6750.html#section-2.1" rel="noopener noreferrer"&gt;RFC 6750, authorization request header field&lt;/a&gt;). HTTP deliberately does not define one universal maximum length for a field line, field value, or header section. Implementations set their own limits, and a server that receives request fields larger than it is willing to process is supposed to reject the request with an appropriate 4xx response, not ignore the fields (&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html#name-field-limits" rel="noopener noreferrer"&gt;RFC 9110, field limits&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That means a perfectly valid access token can still be operationally too large for one proxy, gateway, service mesh sidecar, WAF, CDN edge, or application server in the path.&lt;/p&gt;

&lt;p&gt;This is not only an availability problem. It is a security design smell. If every delegated hop adds nested actors, subjects, issuers, audiences, or other chain evidence, the token can grow until one component in the HTTP path handles it differently than the component that issued it or the API that validates it. Best case, the call fails cleanly. Worse case, a proxy truncates, drops, rewrites, retries, normalizes, caches, or falls back in a way the authorization model never expected. HTTP itself warns that ignoring oversized header fields increases exposure to request smuggling, and its security considerations explicitly call out attacks via protocol element length (&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html#name-field-limits" rel="noopener noreferrer"&gt;RFC 9110, field limits&lt;/a&gt;, &lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html#name-attacks-via-protocol-elemen" rel="noopener noreferrer"&gt;RFC 9110, attacks via protocol element length&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;This is how a governance problem becomes an incident report. The identity system issued a token that was cryptographically valid. The architecture allowed a chain long enough to make that token huge. The application team assumed middleware would fail closed. The proxy team assumed the upstream API owned authorization. Then an agent reaches data it should not see because one hop in the path handled an oversized credential differently than another.&lt;/p&gt;

&lt;p&gt;Who owns that failure?&lt;/p&gt;

&lt;p&gt;The vendor that supported unbounded chaining? The architect who allowed policies with no practical chain-depth ceiling? The developer who never measured token size or actor-chain depth? The HTTP proxy that mishandled an oversized &lt;code&gt;Authorization&lt;/code&gt; header? The operations team that changed a WAF limit two months ago and did not know it was now part of the identity control plane?&lt;/p&gt;

&lt;p&gt;This is why "it worked two months ago" is such a dangerous sentence in agent identity. Nothing in the agent code needs to change for the chain to become too long. More tools get added. More agents call each other. More claims get preserved for audit. More tenants and issuers appear. Suddenly the same logical flow produces a larger token, crosses a different intermediary, and starts failing. Or worse, failing open.&lt;/p&gt;

&lt;p&gt;But a representable chain is not automatically a governable chain.&lt;/p&gt;

&lt;p&gt;That is the industry's current problem in one sentence: we can describe the chain better than we can govern the chain.&lt;/p&gt;

&lt;h2&gt;
  
  
  Evidence helps, but it does not create trust
&lt;/h2&gt;

&lt;p&gt;Once the conversation moves from "can I get a token?" to "can the downstream system understand the context?" teams naturally reach for more data in the token.&lt;/p&gt;

&lt;p&gt;That instinct is reasonable, but it is also incomplete. A downstream resource may need to know whether the caller is an agent, which agent is acting, which human user is present, which tenant owns the agent, whether the human is external to the resource tenant, or whether the agent is multi-tenant. That context can make downstream decisions smarter.&lt;/p&gt;

&lt;p&gt;But more evidence in a token is not the same as trust.&lt;/p&gt;

&lt;p&gt;Adding agent context, actor context, tenant context, or multi-tenant application context can make a downstream authorization decision more informed. It does not answer who approved the mapping, whether the source domain is trusted, whether the chain is still valid, or how the receiving domain should react when a risk signal changes.&lt;/p&gt;

&lt;p&gt;That is the mistake to avoid. Token evidence can describe what arrived. Governance decides whether what arrived should be trusted.&lt;/p&gt;

&lt;p&gt;The difference matters. Carrying context is easier than proving that the context should be trusted across organizational, identity provider, and agent boundaries.&lt;/p&gt;

&lt;h2&gt;
  
  
  The emerging standards conversation is really about trust evidence
&lt;/h2&gt;

&lt;p&gt;When people ask for "the standard," they often mean one protocol that makes the whole diagram safe. That is not how this will land.&lt;/p&gt;

&lt;p&gt;The industry is moving along several tracks at once.&lt;/p&gt;

&lt;p&gt;OAuth token exchange gives us the exchange grammar (&lt;a href="https://www.rfc-editor.org/rfc/rfc8693.html" rel="noopener noreferrer"&gt;RFC 8693&lt;/a&gt;). Workload identity federation gives us a workable model for autonomous workload trust without secrets (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;workload identity federation&lt;/a&gt;). SPIFFE gives us an identity framework for workloads in heterogeneous environments, including JWT and X.509 identity documents (&lt;a href="https://spiffe.io/docs/latest/spiffe-about/overview/" rel="noopener noreferrer"&gt;SPIFFE overview&lt;/a&gt;). Shared Signals and CAEP give the ecosystem a way to communicate security events and state changes continuously, rather than treating access decisions as one-time snapshots (&lt;a href="https://openid.net/wg/sharedsignals/" rel="noopener noreferrer"&gt;OpenID Shared Signals Working Group&lt;/a&gt;). Agent protocols such as MCP and A2A are pushing tool and agent communication into more formal shapes, and Microsoft explicitly lists OAuth 2.0, MCP, and A2A among the protocols supported by the Entra Agent identity platform (&lt;a href="https://learn.microsoft.com/entra/agent-id/what-is-microsoft-entra-agent-id" rel="noopener noreferrer"&gt;Microsoft Entra Agent ID&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The center of gravity is shifting from "can I exchange a token?" to "what evidence travels with the token, and who is accountable for trusting it?"&lt;/p&gt;

&lt;p&gt;That is the right shift.&lt;/p&gt;

&lt;p&gt;The next generation of standards work has to answer questions the current protocol layer only exposes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;How does a receiving domain know the original user is still the same user after multiple agent and tool hops?&lt;/li&gt;
&lt;li&gt;How does the chain preserve both the human subject and the current agent actor without collapsing into impersonation?&lt;/li&gt;
&lt;li&gt;How are identity provider mappings approved, reviewed, expired, and revoked?&lt;/li&gt;
&lt;li&gt;How does a downstream resource distinguish a first-party enterprise agent from a multi-tenant external agent acting for an enterprise user?&lt;/li&gt;
&lt;li&gt;How do risk signals travel fast enough that a compromised account or agent does not keep operating on stale trust?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;None of those questions are solved by adding one more token parameter.&lt;/p&gt;

&lt;p&gt;They require standards that combine identity evidence, delegation semantics, risk signaling, authorization policy, and observability.&lt;/p&gt;

&lt;h2&gt;
  
  
  What architects should do now
&lt;/h2&gt;

&lt;p&gt;The correct response is not to wait for the perfect standard. It is to design with the current product and trust boundary in mind.&lt;/p&gt;

&lt;p&gt;First, separate platform location from identity provider domain. An agent running in AWS can still use Microsoft Entra Agent ID. An agent running in Azure can still call a Ping-protected resource. The host is an implementation detail. The issuer, audience, subject, actor, and trust mapping are the security model (&lt;a href="https://learn.microsoft.com/entra/agent-id/configure-third-party-agents" rel="noopener noreferrer"&gt;third-party agent integration patterns&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Second, split autonomous agent flows from user-delegated flows before you draw the final architecture. Autonomous flows can be built today with client credentials, certificates, federated identity credentials, managed identities, workload identity federation, and standard token validation. A sidecar can help keep token acquisition and credential handling out of the agent code, but it is not itself the agent authentication method.&lt;/p&gt;

&lt;p&gt;The delegated case is different. With Entra Agent ID today, do not design for an OBO flow that carries Microsoft Entra user context across an identity provider boundary and expects the other domain to preserve that context as part of the same delegation chain. Entra Agent ID supports OBO patterns for agents, but cross-IDP user-context propagation is not the supported operating model (&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-on-behalf-of-oauth-flow" rel="noopener noreferrer"&gt;Agent OAuth OBO flow&lt;/a&gt;). If the downstream system is governed by another identity provider, treat that as a separate trust domain. Either keep the delegated path inside one identity provider boundary, use autonomous workload-to-workload trust where user context is not required, or design an explicit application-level handoff with its own audit and policy model.&lt;/p&gt;

&lt;p&gt;Third, avoid cross-domain impersonation unless the business process genuinely requires it and the audit model can survive scrutiny. Delegation should preserve the actor. Impersonation hides the actor from the resource's practical decision point. For agents, hiding the actor is usually the wrong instinct (&lt;a href="https://www.rfc-editor.org/rfc/rfc8693.html#section-1.1" rel="noopener noreferrer"&gt;RFC 8693 delegation vs. impersonation&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The real standard we are missing
&lt;/h2&gt;

&lt;p&gt;The industry does not lack token standards. It lacks a complete, interoperable &lt;em&gt;agent identity propagation governance model&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;That model must preserve the original user where appropriate, preserve the current agent actor always, make cross-IDP mappings explicit, carry enough evidence for downstream decisions, propagate risk and revocation signals, and give enterprises a way to audit who allowed which transition.&lt;/p&gt;

&lt;p&gt;OAuth token exchange is part of that answer. Workload identity federation is part of that answer. SPIFFE is part of that answer. Shared Signals and CAEP are part of that answer. Agent-specific identity platforms such as Microsoft Entra Agent ID are part of that answer.&lt;/p&gt;

&lt;p&gt;But no single one of them is the whole answer yet.&lt;/p&gt;

&lt;p&gt;That is the uncomfortable but useful conclusion from the diagram. We can build useful agent identity systems today. We can secure autonomous agents today. We can do delegated agent flows inside well-understood trust boundaries today. We can even cross platforms without much drama when the flow is autonomous or the trust boundary is explicit.&lt;/p&gt;

&lt;p&gt;The thing we cannot hand-wave is multi-hop, user-bound, cross-IDP identity propagation.&lt;/p&gt;

&lt;p&gt;That is the work ahead. Not just for Microsoft. For the industry.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/what-is-microsoft-entra-agent-id" rel="noopener noreferrer"&gt;Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/what-are-agent-identities" rel="noopener noreferrer"&gt;What are agent identities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/configure-third-party-agents" rel="noopener noreferrer"&gt;Integrate third-party agents with Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/authentication-with-auth-sdk-sidecar" rel="noopener noreferrer"&gt;Authentication with Microsoft Entra Auth SDK sidecar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-on-behalf-of-oauth-flow" rel="noopener noreferrer"&gt;Agent OAuth flows: on behalf of flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;Microsoft Entra workload identity federation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-protocols-oidc" rel="noopener noreferrer"&gt;Microsoft identity platform and OpenID Connect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8693.html" rel="noopener noreferrer"&gt;OAuth 2.0 Token Exchange, RFC 8693&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6750.html" rel="noopener noreferrer"&gt;OAuth 2.0 Bearer Token Usage, RFC 6750&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9110.html" rel="noopener noreferrer"&gt;HTTP Semantics, RFC 9110&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openid.net/specs/openid-connect-core-1_0.html" rel="noopener noreferrer"&gt;OpenID Connect Core 1.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://spiffe.io/docs/latest/spiffe-about/overview/" rel="noopener noreferrer"&gt;SPIFFE overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://openid.net/wg/sharedsignals/" rel="noopener noreferrer"&gt;OpenID Shared Signals Working Group&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>entraagentid</category>
      <category>identitystandards</category>
      <category>oauth</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>Access packages are the control plane for Entra Agent ID permissions</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Thu, 09 Jul 2026 10:43:00 +0000</pubDate>
      <link>https://dev.to/astaykov/access-packages-are-the-control-plane-for-entra-agent-id-permissions-4kph</link>
      <guid>https://dev.to/astaykov/access-packages-are-the-control-plane-for-entra-agent-id-permissions-4kph</guid>
      <description>&lt;p&gt;It has been a couple of months since &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/key-concepts" rel="noopener noreferrer"&gt;Microsoft Entra Agent ID became generally available&lt;/a&gt;, but the same message keeps landing in my inbox: "How do we grant permissions to an agent identity? There is no API Permissions page in Entra."&lt;/p&gt;

&lt;p&gt;That moment is always interesting. The team thinks they found a product gap. What they actually found is a mental-model gap.&lt;/p&gt;

&lt;p&gt;Most engineering organizations still design identity the old way: define a permission set early, grant it once, and treat that grant as stable infrastructure. That was already brittle for conventional apps. For agents, it is worse. An agent is useful &lt;em&gt;because&lt;/em&gt; it encounters new contexts, new tools, and new data surfaces after go-live.&lt;/p&gt;

&lt;p&gt;In &lt;a href="https://dev.to/astaykov/a-transformational-ai-agent-does-not-start-with-the-permissions-it-needs-4g8l"&gt;A transformational AI agent does not start with the permissions it needs&lt;/a&gt;, I argued that serious agents should start narrow and &lt;em&gt;earn&lt;/em&gt; scope over time. This article is the governance architecture behind that claim.&lt;/p&gt;

&lt;h2&gt;
  
  
  The portal is not missing a button, your architecture is missing a layer
&lt;/h2&gt;

&lt;p&gt;Entra Agent ID does not erase consent. It does not replace consent. It does not invent a magical fourth mode of consent.&lt;/p&gt;

&lt;p&gt;Consent fundamentals remain exactly what Microsoft documents for enterprise applications: &lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview" rel="noopener noreferrer"&gt;user consent and admin consent are still the core model&lt;/a&gt;. If delegated access is allowed by policy, a user can authorize it. If risk or scope requires stronger control, admin consent is required.&lt;/p&gt;

&lt;p&gt;That is the first important correction: the "no API permissions blade" observation should not lead to panic. It should lead to design. Agent identities are still bound to Entra authorization rules, including the &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/authorization-agent-id" rel="noopener noreferrer"&gt;Agent ID authorization model for delegated and application permissions&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The missing thing is not a tab. The missing thing is an &lt;em&gt;operating path&lt;/em&gt; for controlled permission growth.&lt;/p&gt;

&lt;h2&gt;
  
  
  Consent semantics are unchanged, enterprise consent posture is still strict
&lt;/h2&gt;

&lt;p&gt;Some architects respond with: "Fine, let users consent as the agent discovers new needs."&lt;/p&gt;

&lt;p&gt;Technically, that is an option in specific policy configurations. Operationally, most enterprises do not live there.&lt;/p&gt;

&lt;p&gt;Real-world tenants often clamp user consent tightly with explicit &lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent?pivots=portal" rel="noopener noreferrer"&gt;tenant-level consent settings&lt;/a&gt;, because delegated access can expose sensitive workloads very quickly when control boundaries are weak. Security teams are not being stubborn here. They are pattern matching from years of experience.&lt;/p&gt;

&lt;p&gt;So we end up with a familiar tension:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AI platform teams need agents that adapt at runtime.&lt;/li&gt;
&lt;li&gt;Governance teams need approvals, accountability, and least-standing-privilege.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you force one side to "win," you get predictable failure modes. Either the agent is over-permissioned up front, or it is blocked by manual ticketing loops every time reality changes.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access packages for agent identities are the bridge
&lt;/h2&gt;

&lt;p&gt;This is where &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-access-packages" rel="noopener noreferrer"&gt;access packages for agent identities&lt;/a&gt; become much more than a governance checkbox.&lt;/p&gt;

&lt;p&gt;Access packages give you a permission supply chain. The agent does not invent access. It requests from a pre-governed catalog. Policy decides if, when, and how access is granted.&lt;/p&gt;

&lt;p&gt;That sounds abstract until you map it to day-to-day architecture decisions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Identity engineers define packages that correspond to meaningful data capabilities.&lt;/li&gt;
&lt;li&gt;Security and compliance teams define who can approve under which conditions.&lt;/li&gt;
&lt;li&gt;Agent runtime requests package assignment only when task context justifies it.&lt;/li&gt;
&lt;li&gt;Lifecycle policy removes or expires access when the need window closes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is exactly the governance direction reflected in &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/agent-id-governance-overview" rel="noopener noreferrer"&gt;Entra guidance for governing agent identities&lt;/a&gt;: agent access is not just about granting once, it is about ongoing controlled assignment.&lt;/p&gt;

&lt;h2&gt;
  
  
  The underrated power move: put consent inside workflow
&lt;/h2&gt;

&lt;p&gt;Most teams still treat admin consent like a one-off privileged ceremony. Someone with rights clicks approve, everyone moves on, and the architecture diagram quietly lies about "least privilege."&lt;/p&gt;

&lt;p&gt;Access packages let you treat that approval as a workflow stage instead of an exception path.&lt;/p&gt;

&lt;p&gt;With entitlement management policies, you can require &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-approval-policy" rel="noopener noreferrer"&gt;multi-stage approvals&lt;/a&gt;, use &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-dynamic-approval" rel="noopener noreferrer"&gt;dynamic approval selection&lt;/a&gt;, enforce &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-incompatible" rel="noopener noreferrer"&gt;separation-of-duties with incompatible packages&lt;/a&gt;, and enforce &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-lifecycle-policy" rel="noopener noreferrer"&gt;expiry and lifecycle controls&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;If that still feels too generic, add custom process hooks through &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-logic-apps-integration" rel="noopener noreferrer"&gt;Logic Apps integration for entitlement management&lt;/a&gt;. That is where you inject business-specific gates: risk score checks, change-ticket correlation, regulatory checks, or post-assignment notifications into your SOC workflow.&lt;/p&gt;

&lt;p&gt;This is why I keep calling access packages an underestimated powerhouse. They are not "just governance UI." They are programmable policy choreography.&lt;/p&gt;

&lt;h2&gt;
  
  
  How this looks in a real request loop
&lt;/h2&gt;

&lt;p&gt;A transformational agent pattern becomes concrete when the request loop is concrete.&lt;/p&gt;

&lt;p&gt;Start with the assumption that the agent is under-permissioned for future unknown tasks. When it hits a boundary, it enters a governed escalation loop:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Determine the missing capability from the failed call and policy context.&lt;/li&gt;
&lt;li&gt;Discover candidate packages from &lt;a href="https://learn.microsoft.com/en-us/graph/api/entitlementmanagement-list-accesspackages?view=graph-rest-1.0" rel="noopener noreferrer"&gt;GET access packages&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Inspect assignment rules through &lt;a href="https://learn.microsoft.com/en-us/graph/api/entitlementmanagement-list-assignmentpolicies?view=graph-rest-1.0" rel="noopener noreferrer"&gt;GET assignment policies&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;If policy allows, submit &lt;a href="https://learn.microsoft.com/en-us/graph/api/entitlementmanagement-post-assignmentrequests?view=graph-rest-1.0" rel="noopener noreferrer"&gt;POST assignment request&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Let approval workflow, incompatibility checks, and lifecycle policy decide the final outcome.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;If you need to inspect package role mappings more deeply, you can also query &lt;a href="https://learn.microsoft.com/en-us/graph/api/accesspackage-list-resourcerolescopes?view=graph-rest-1.0" rel="noopener noreferrer"&gt;resource role scopes in an access package&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Notice the key architectural boundary: the agent can &lt;em&gt;request&lt;/em&gt; escalation, but it cannot bypass governance. That is the whole point.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters more than one Entra feature
&lt;/h2&gt;

&lt;p&gt;If we are honest, most early agent pilots still hide one uncomfortable truth: broad standing access was pre-granted so the demo would not fail in front of stakeholders.&lt;/p&gt;

&lt;p&gt;That shortcut does not scale. It becomes technical debt with legal exposure.&lt;/p&gt;

&lt;p&gt;The better model is plain:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;capability discovery can be dynamic,&lt;/li&gt;
&lt;li&gt;approval can be policy-bound,&lt;/li&gt;
&lt;li&gt;access can be time-bound,&lt;/li&gt;
&lt;li&gt;accountability can be explicit.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination is exactly what transformational agents need. They can evolve without becoming permanent over-privileged identities.&lt;/p&gt;

&lt;h2&gt;
  
  
  The decision point for architecture teams
&lt;/h2&gt;

&lt;p&gt;If your operating model is "we will manually update permissions whenever the agent needs something new," you do not have an agent permission model. You have a backlog with production blast radius.&lt;/p&gt;

&lt;p&gt;If your operating model is "new access must flow through access packages with policy-owned approvals and expiry," then you have something that can survive beyond a pilot and withstand audit pressure.&lt;/p&gt;

&lt;p&gt;That is the shift in one sentence: agent autonomy should grow through governed workflow, not through standing privilege.&lt;/p&gt;

&lt;p&gt;This is why the "missing API Permissions tab" conversation is useful. It forces the right question. Not "where do I click to grant more?" but "what control plane governs permission growth in my tenant?"&lt;/p&gt;

&lt;p&gt;For Entra Agent ID in enterprise reality, the best answer today is access packages.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/key-concepts" rel="noopener noreferrer"&gt;Microsoft Entra Agent ID key concepts&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/authorization-agent-id" rel="noopener noreferrer"&gt;Authorization in Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/user-admin-consent-overview" rel="noopener noreferrer"&gt;Overview of user and admin consent&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/enterprise-apps/configure-user-consent?pivots=portal" rel="noopener noreferrer"&gt;Configure how users consent to applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-access-packages" rel="noopener noreferrer"&gt;Access packages for agent identities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/agent-id-governance-overview" rel="noopener noreferrer"&gt;Governing agent identities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-approval-policy" rel="noopener noreferrer"&gt;Configure approval policy for an access package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-dynamic-approval" rel="noopener noreferrer"&gt;Configure dynamic approvals in entitlement management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-incompatible" rel="noopener noreferrer"&gt;Configure separation of duties checks for access packages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-access-package-lifecycle-policy" rel="noopener noreferrer"&gt;Change lifecycle settings for an access package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-logic-apps-integration" rel="noopener noreferrer"&gt;Trigger Logic Apps with custom extensions in entitlement management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/graph/api/entitlementmanagement-list-accesspackages?view=graph-rest-1.0" rel="noopener noreferrer"&gt;List access packages&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/graph/api/entitlementmanagement-list-assignmentpolicies?view=graph-rest-1.0" rel="noopener noreferrer"&gt;List assignment policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/graph/api/entitlementmanagement-post-assignmentrequests?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Create access package assignment request&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/graph/api/accesspackage-list-resourcerolescopes?view=graph-rest-1.0" rel="noopener noreferrer"&gt;List resource role scopes in an access package&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>entraagentid</category>
      <category>identitygovernance</category>
      <category>microsoftentra</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>Every change to an Entra extension is a Control Plane event: the monitoring contract</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 07 Jul 2026 09:47:00 +0000</pubDate>
      <link>https://dev.to/astaykov/every-change-to-an-entra-extension-is-a-control-plane-event-the-monitoring-contract-5621</link>
      <guid>https://dev.to/astaykov/every-change-to-an-entra-extension-is-a-control-plane-event-the-monitoring-contract-5621</guid>
      <description>&lt;p&gt;Parts 1 and 2 of this series (&lt;a href="https://dev.to/astaykov/microsoft-entra-extensibility-is-a-gift-it-is-also-control-plane-4egn"&gt;Microsoft Entra extensibility is a gift. It is also Control Plane.&lt;/a&gt; and &lt;a href="https://dev.to/astaykov/securing-the-code-that-decides-who-entra-trusts-a-function-app-and-a-logic-app-end-to-end-1bi0"&gt;Securing the code that decides who Entra trusts&lt;/a&gt;) made two static decisions. &lt;em&gt;Where&lt;/em&gt; the code lives: a dedicated Control Plane subscription, directly under the root management group or under a dedicated Control Plane management group, never in the platform identity subscription or an application landing zone. &lt;em&gt;What credential&lt;/em&gt; it uses to call out: a managed identity by default, &lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;federated identity credentials&lt;/a&gt; when the call must leave Azure, certificates as a tolerated middle step, and static symmetric keys never.&lt;/p&gt;

&lt;p&gt;Both decisions are one-time. You make them, you walk away, you do not touch them for months.&lt;/p&gt;

&lt;p&gt;The third decision is not like that. It is continuous, and it is the one most teams quietly skip: how do you know the deployed code on that Function App is still the code your reviewers approved? How do you know the Logic App workflow definition has not been rewritten since last Tuesday? How do you know nobody added a federated identity credential to the managed identity at 3 a.m. on a Saturday?&lt;/p&gt;

&lt;p&gt;The answer is monitoring. Not "we have Log Analytics turned on." Monitoring with a specific operating contract attached.&lt;/p&gt;

&lt;h2&gt;
  
  
  The posture inversion
&lt;/h2&gt;

&lt;p&gt;For most Azure workloads, the default operating posture is reasonable trust. Engineers deploy. Pipelines run. Configuration drifts a little. The team reviews changes weekly. Anomalies are caught eventually.&lt;/p&gt;

&lt;p&gt;For a Microsoft Entra extension, that posture is wrong. The default has to be inverted.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;Once an Entra extension lands in production, every change to it is suspicious by default.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Not "needs review." Not "let's check first." Suspicious. The default state of an alert firing on a Function App that hosts a &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/custom-extension-overview" rel="noopener noreferrer"&gt;custom claims provider&lt;/a&gt; is "the SOC is investigating, prove this was approved." If you cannot prove the change was approved within the team's response SLA, the change is treated as an incident and rolled back. That posture is harsh on purpose. The same RBAC inheritance chain that lets a Contributor four management groups above your Function App quietly replace its code (the central argument of Part 1) is what forces the inverted default. You cannot rely on the deployment surface to be small. You have to rely on the alerting surface to be loud.&lt;/p&gt;

&lt;p&gt;That inversion is the monitoring contract this article is about.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three change surfaces
&lt;/h2&gt;

&lt;p&gt;A production Entra extension has three change surfaces, and each one needs its own forced-diagnostics control, its own alert, and its own row on the operating contract.&lt;/p&gt;

&lt;h3&gt;
  
  
  Surface 1: the runtime itself
&lt;/h3&gt;

&lt;p&gt;This is the surface most teams think of first, and the one most teams under-instrument. The deployed code on the Function App. The workflow definition on the Logic App. The App Settings. The environment variables. The runtime version. The configured custom domain. Each of these is a knob that, if turned, changes the behavior of the extension the next time Entra calls it.&lt;/p&gt;

&lt;p&gt;For the custom claims provider Function App, the relevant Azure Resource Manager operations to alert on are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.Web/sites/extensions/write&lt;/code&gt; (a new deployment was pushed).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.Web/sites/config/appsettings/write&lt;/code&gt; (App Settings were changed, including any reference to a Key Vault secret).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.Web/sites/config/web/write&lt;/code&gt; (runtime version, startup command, or general configuration was changed).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.Web/sites/publishxml/action&lt;/code&gt; (publish profile credentials were generated, which is one of the easiest ways to deploy code outside the normal pipeline).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the dynamic approval Logic App, the analogous operations are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.Logic/workflows/write&lt;/code&gt; (the workflow definition itself was modified).&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.Logic/workflows/connections/write&lt;/code&gt; and &lt;code&gt;Microsoft.Web/connections/write&lt;/code&gt; (an API connection was created or modified, including the authentication target it points at).&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each of these should be backed by an &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/activity-log-alerts" rel="noopener noreferrer"&gt;Activity Log alert&lt;/a&gt; scoped to the Control Plane subscription, with an action group that routes to the SOC's primary intake. Not to the team's chat channel. Not to an email list. The SOC's intake.&lt;/p&gt;

&lt;h3&gt;
  
  
  Surface 2: identity and RBAC
&lt;/h3&gt;

&lt;p&gt;This is the surface that does not look like the extension at all and is therefore the easiest to ignore. The extension is unchanged. The blast radius around it is what changed.&lt;/p&gt;

&lt;p&gt;Specific things to alert on, across the Control Plane subscription:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.Authorization/roleAssignments/write&lt;/code&gt; anywhere on the inheritance path, from the resource up to the subscription. A Contributor (or User Access Administrator, or Owner) just appeared on a tier in the chain. Walk the inheritance (&lt;a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/overview" rel="noopener noreferrer"&gt;Azure RBAC overview&lt;/a&gt;) and ask: does this new principal now have effective Contributor on the Function App? If yes, the SOC needs to know.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.ManagedIdentity/userAssignedIdentities/federatedIdentityCredentials/write&lt;/code&gt; on any user-assigned managed identity in scope. A new &lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;federated identity credential&lt;/a&gt; is, by construction, a new way to act as that identity from outside Azure. Treat it as a credential issuance event, because that is what it is.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;Microsoft.KeyVault/vaults/accessPolicies/write&lt;/code&gt; and &lt;code&gt;Microsoft.Authorization/roleAssignments/write&lt;/code&gt; scoped to the Key Vault. Same logic as the RBAC inheritance, sharper consequence: Owner on the Key Vault is operationally Owner of every certificate in it.&lt;/li&gt;
&lt;li&gt;App registration credential events in the Entra audit log. The Function App and the Logic App that back the extension are both fronted by app registrations or managed identities in Entra. A new client secret, certificate, or federated credential added to that app registration is an alert-worthy event, full stop. The &lt;a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-audit-logs" rel="noopener noreferrer"&gt;Entra audit logs&lt;/a&gt; capture these as &lt;code&gt;Update application – Certificates and secrets management&lt;/code&gt; events; pipe them into the same SOC intake.&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Surface 3: the Entra-side binding
&lt;/h3&gt;

&lt;p&gt;This surface lives entirely in Entra, not in Azure. The custom claims provider is &lt;em&gt;bound&lt;/em&gt; to the application through a custom authentication extension configuration. The dynamic approval Logic App is &lt;em&gt;bound&lt;/em&gt; to an access package through an entitlement management custom extension. Those bindings are what make Entra actually call your code. Change the binding, and Entra now calls somebody else's code instead.&lt;/p&gt;

&lt;p&gt;Two specific events worth their own alerts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Changes to custom authentication extensions in the Entra audit log: the endpoint URL was repointed, the API authentication configuration was changed, the extension was disabled.&lt;/li&gt;
&lt;li&gt;Changes to entitlement management custom extensions: a new callback URL, a new authentication method, a change to the access package policy that consumes the extension.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The same Entra audit log feed that the IGA team already monitors covers most of this. The extra step is making sure the SOC sees it alongside the Azure Activity Log events, because the correlation between "the Entra binding moved" and "a deployment happened on the new target one hour earlier" is exactly the story you want analysts to be able to tell.&lt;/p&gt;

&lt;h2&gt;
  
  
  Forced diagnostics: making the signal undeniable
&lt;/h2&gt;

&lt;p&gt;Alerts only fire if the signal reaches the alert engine. Three controls turn diagnostics from "we enabled it on most resources" into a property of the subscription itself.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Policy with &lt;code&gt;deployIfNotExists&lt;/code&gt; for diagnostic settings.&lt;/strong&gt; Apply &lt;a href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/effect-deploy-if-not-exists" rel="noopener noreferrer"&gt;built-in or custom policies&lt;/a&gt; at the Control Plane management group that force every Function App, every Logic App, every Key Vault, every Storage Account, and every Managed Identity created underneath it to ship diagnostic logs to a dedicated tenant-level &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings" rel="noopener noreferrer"&gt;Log Analytics workspace&lt;/a&gt;. The policy is &lt;code&gt;deployIfNotExists&lt;/code&gt;, not &lt;code&gt;audit&lt;/code&gt;, so a missing diagnostic setting is repaired by the platform, not just reported on. There are existing built-ins covering Logic Apps and many resource types; custom definitions cover the rest. The principle is: it is not optional, and it is not possible to deploy a resource into this management group without diagnostic settings.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Azure Policy &lt;code&gt;deny&lt;/code&gt; for tampering with diagnostic settings.&lt;/strong&gt; A &lt;code&gt;deployIfNotExists&lt;/code&gt; policy creates the setting. A complementary policy denies its modification or removal by anyone except a tightly-scoped platform identity. If an engineer cannot turn the diagnostic setting off, an attacker who compromises that engineer cannot either.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Resource Locks on the resource groups that host the extensions.&lt;/strong&gt; &lt;a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/lock-resources" rel="noopener noreferrer"&gt;Resource Locks&lt;/a&gt; at &lt;code&gt;CanNotDelete&lt;/code&gt; for everything, &lt;code&gt;ReadOnly&lt;/code&gt; for the resource groups that hold the runtime resources, applied at the resource-group level. ReadOnly does not prevent code deployments through the Function App's own publish endpoint, so it is not a substitute for the alert on &lt;code&gt;Microsoft.Web/sites/extensions/write&lt;/code&gt;, but it does block the entire class of "an attacker recreates the resource with the same name and a different definition" attack.&lt;/p&gt;

&lt;p&gt;Layered: policy forces diagnostics on, policy prevents diagnostics from being turned off, resource locks prevent the resources from being deleted out from under the diagnostics, alerts on the resulting log stream surface every change. Each control compensates for the failure modes of the others.&lt;/p&gt;

&lt;h2&gt;
  
  
  Correlation: the story the SIEM has to be able to tell
&lt;/h2&gt;

&lt;p&gt;Each individual alert is signal. The value lives in the correlation across feeds. The SOC should be able to ask, and answer, questions like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A token issued by Entra at 14:07 carried a claim asserting "department=Finance." The custom claims provider for that application is hosted on Function App &lt;code&gt;fa-claims-prod&lt;/code&gt;. Did the deployed code on &lt;code&gt;fa-claims-prod&lt;/code&gt; change in the last 24 hours? Did its App Settings? Did any role assignment land on it, its resource group, its subscription, or any management group above it?&lt;/li&gt;
&lt;li&gt;An access package request was auto-approved at 09:21 for a privileged group. The dynamic approval Logic App that decided is &lt;code&gt;la-dynapproval-prod&lt;/code&gt;. Did the workflow definition change in the last 7 days? Did any of its API connections get repointed? Did the Logic App's managed identity get a new federated credential?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Both questions are answerable with &lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/platform-logs-overview" rel="noopener noreferrer"&gt;Azure Activity Log&lt;/a&gt;, Entra audit log, Function App and Logic App resource logs, and the extension's own application logs, all sitting in one Log Analytics workspace, with &lt;a href="https://learn.microsoft.com/en-us/azure/sentinel/data-connectors/microsoft-entra-id" rel="noopener noreferrer"&gt;Microsoft Sentinel&lt;/a&gt; on top stitching them together. The Sentinel queries are the easy part. The hard part is making sure the three feeds are guaranteed to be there, which is exactly what the forced diagnostics controls deliver.&lt;/p&gt;

&lt;h2&gt;
  
  
  The operating contract: three states, not four
&lt;/h2&gt;

&lt;p&gt;Every alert from any of the three change surfaces lands in one of exactly three buckets. There is no fourth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Approved change.&lt;/strong&gt; The deployment was pre-declared in the change management system. The change record carries the artifact hash, the pipeline run ID, the requesting engineer, and a declared start and end window. The alert correlates to the change record automatically (on pipeline run ID, on deployment principal, on time window) and is closed within minutes. The audit trail shows the alert, the correlation, and the closure.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Informational platform change.&lt;/strong&gt; Microsoft platform-driven activity that you do not control: automatic certificate rotations on platform-managed components, Microsoft-side runtime version refreshes, Azure-managed identity key rotations, and similar. These still ship to the same workspace and still fire as Activity Log entries, but they route to a low-severity "platform" queue, not to the SOC's primary intake. Two rules govern this bucket: the events must be attributable to a known Microsoft service principal or platform operation (the actor identity on the Activity Log entry is non-human and matches an allow-listed Microsoft pattern), and the bucket must be reviewed at least weekly so it does not become a quiet hiding place. Anything that does not cleanly match a platform pattern falls through to the third bucket by default.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Incident.&lt;/strong&gt; Anything that is neither pre-declared by a change record nor cleanly attributable to a Microsoft platform operation. SOC intake, paged on-call, automatic rollback if the runtime change can be reverted, manual investigation either way. The default reaction is to revert first and investigate after. The cost of a wrongly-reverted legitimate change is a Slack apology. The cost of a wrongly-allowed malicious change is a forged claim in a token your applications already trusted.&lt;/p&gt;

&lt;p&gt;That is the entire operating model. Approved, informational-platform, or incident. No "we will look at it later." No "probably the platform team." If it does not fit one of the first two buckets within a tight automatic correlation window, it is bucket three.&lt;/p&gt;

&lt;h2&gt;
  
  
  The three-legged stool
&lt;/h2&gt;

&lt;p&gt;Part 1 placed the resources correctly. Part 2 made sure they authenticate without leaking secrets. Part 3 makes sure that, once they are running, every change to them is either justified or visibly investigated. None of the three legs stands alone. A correctly placed resource without monitoring is one quiet deployment away from being someone else's resource. A perfectly monitored resource using a static API key is monitoring around an already-compromised credential. A managed identity in the wrong subscription is a credential model wasted on an over-permissive blast radius.&lt;/p&gt;

&lt;p&gt;Built together, they are how Entra extensibility stays the thing that makes Entra a great product, rather than the thing that quietly hollowed it out. Use the extensibility. Put the code where it belongs. Authenticate it with the right credentials. And treat every change to it, from the first day it is in production, as a Control Plane event worth proving.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/custom-extension-overview" rel="noopener noreferrer"&gt;Custom authentication extensions overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/diagnostic-settings" rel="noopener noreferrer"&gt;Diagnostic settings in Azure Monitor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/essentials/platform-logs-overview" rel="noopener noreferrer"&gt;Azure Monitor platform logs overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/governance/policy/concepts/effect-deploy-if-not-exists" rel="noopener noreferrer"&gt;Azure Policy &lt;code&gt;deployIfNotExists&lt;/code&gt; effect&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/activity-log-alerts" rel="noopener noreferrer"&gt;Activity log alerts in Azure Monitor&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-monitor/alerts/alerts-types" rel="noopener noreferrer"&gt;Azure Monitor alert types&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/lock-resources" rel="noopener noreferrer"&gt;Lock your resources to protect your infrastructure (Resource Locks)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/monitoring-health/concept-audit-logs" rel="noopener noreferrer"&gt;Microsoft Entra audit logs&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/sentinel/data-connectors/microsoft-entra-id" rel="noopener noreferrer"&gt;Microsoft Entra ID data connector for Microsoft Sentinel&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/graph/api/resources/federatedidentitycredentials-overview?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Federated Identity Credentials overview (Microsoft Graph)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/overview" rel="noopener noreferrer"&gt;Azure RBAC overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;Workload Identity Federation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>microsoftentra</category>
      <category>azuremonitor</category>
      <category>zerotrust</category>
      <category>secops</category>
    </item>
    <item>
      <title>Securing the code that decides who Entra trusts: a Function App and a Logic App, end to end</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Thu, 02 Jul 2026 10:37:00 +0000</pubDate>
      <link>https://dev.to/astaykov/securing-the-code-that-decides-who-entra-trusts-a-function-app-and-a-logic-app-end-to-end-1bi0</link>
      <guid>https://dev.to/astaykov/securing-the-code-that-decides-who-entra-trusts-a-function-app-and-a-logic-app-end-to-end-1bi0</guid>
      <description>&lt;p&gt;Part 1 of this series, [[Entra-Extensibility-Is-Control-Plane]], made the case that every Microsoft Entra extension point hands a piece of the Entra trust boundary to a system Entra does not own. The Azure resources that host that code sit on the Control Plane (in the modern Microsoft &lt;a href="https://learn.microsoft.com/en-us/security/privileged-access-workstations/privileged-access-access-model" rel="noopener noreferrer"&gt;Enterprise Access Model&lt;/a&gt;, what the legacy tiered model called Tier 0). The Azure RBAC inheritance chain makes them dangerously easy to compromise from far up the management-group tree, and they deserve a placement and a guardrail set normally reserved for domain controllers and Entra Connect.&lt;/p&gt;

&lt;p&gt;This article gets concrete. Two real extension scenarios, end to end, and then the question that decides the security of both: what credential does the extension use when it calls out?&lt;/p&gt;

&lt;h2&gt;
  
  
  Example A: a custom claims provider backed by an Azure Function
&lt;/h2&gt;

&lt;p&gt;The first scenario is the one most likely to land in a production tenant in the next six months. A team needs to inject custom claims into the access tokens issued to their applications. The supported path is a &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/custom-extension-overview" rel="noopener noreferrer"&gt;custom claims provider&lt;/a&gt;, implemented as a REST endpoint that Entra calls during the token issuance event. The reference implementation is an Azure Function.&lt;/p&gt;

&lt;p&gt;The flow, in one paragraph. A user signs in to an application registered in Entra. Entra's token issuance pipeline reaches the configured custom claims provider extension. Entra calls the Function App's HTTPS endpoint with a server-to-server access token in the &lt;code&gt;Authorization&lt;/code&gt; header, exactly as documented in &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/custom-extension-overview" rel="noopener noreferrer"&gt;the step that covers protecting your Azure Function&lt;/a&gt;. The Function validates the incoming token (issuer, audience, signature), inspects the request body, calls whatever upstream sources it needs to resolve the claims it cares about, and returns a JSON response describing which claims to add to the outgoing token. Entra builds the access token, including the returned claims, and hands it to the relying application. The application makes authorization decisions based on those claims.&lt;/p&gt;

&lt;p&gt;Read that one more time. &lt;em&gt;The application makes authorization decisions based on those claims.&lt;/em&gt; The Function App controls what your applications see. A compromised Function App is, in practical terms, a claim forger sitting inside your token issuance pipeline. Whatever an attacker can put in a returned claim, the downstream application will trust as if Entra had asserted it.&lt;/p&gt;

&lt;p&gt;The blast radius of that Function App is therefore the union of everything that can change what code runs in it:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The source repository. Branch protection, code-owner approvals, the list of users with push rights to &lt;code&gt;main&lt;/code&gt;, the list of users with admin rights on the repository who can bypass protection, and every CI configuration file that ships with the repo.&lt;/li&gt;
&lt;li&gt;The CI/CD pipeline definition. Who can edit it, who can re-run it with arbitrary inputs, who can change which artifact gets deployed at the end of a run.&lt;/li&gt;
&lt;li&gt;The identity the pipeline uses to deploy to Azure. This is the credential that lets the pipeline rewrite the Function App's code at any time. If that credential is a client secret stored as a CI variable, the Function App's code is one leaked variable away from being replaced.&lt;/li&gt;
&lt;li&gt;The Function App resource itself, and every security principal that has Website Contributor, Contributor, or Owner on it directly. These principals can replace the deployed ZIP without ever touching the repository.&lt;/li&gt;
&lt;li&gt;The resource group, the subscription, every management group above the subscription, and the root. As &lt;a href="https://dev.to/astaykov/microsoft-entra-extensibility-is-a-gift-it-is-also-control-plane-4egn"&gt;Part 1&lt;/a&gt; walked, Contributor at any level inherits down. A Contributor (any role) on the root management group does not need to be a member of your identity team to replace your Function App's code.&lt;/li&gt;
&lt;li&gt;The Key Vault holding any secrets, certificates, or connection strings the Function uses to talk to upstream systems. Owner on that Key Vault is, for practical purposes, Owner on the Function's ability to authenticate outbound.&lt;/li&gt;
&lt;li&gt;The App Service Plan, because deployment slots, scaling rules, and runtime versions sit at that scope.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A real custom claims provider in production has to harden every item in that list. Missing any one of them is enough.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example B: a Logic App backing dynamic approval for an access package
&lt;/h2&gt;

&lt;p&gt;The second scenario is governance-shaped. An access package in &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-logic-apps-integration" rel="noopener noreferrer"&gt;Microsoft Entra entitlement management&lt;/a&gt; is configured to use &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-dynamic-approval" rel="noopener noreferrer"&gt;dynamic approval&lt;/a&gt;, so that the approver of a request is selected at runtime by an Azure Logic App rather than statically declared in the policy.&lt;/p&gt;

&lt;p&gt;The flow. A user requests assignment to the access package. Entitlement management invokes the configured Logic App, passing context about the requestor and the package. The Logic App runs whatever workflow you have authored. Typically that workflow needs to call Microsoft Graph (to resolve the requestor's manager, project assignment, or group membership) and at least one external system (a leave or vacation calendar, a shift roster, an IT change management board, a project allocation tool). The Logic App returns the identity of the human or group who should approve, or in some variants it short-circuits and returns an approval or denial directly. Entitlement management then routes the request accordingly.&lt;/p&gt;

&lt;p&gt;The Logic App is now part of your access governance. If an attacker can rewrite the workflow, the attacker can name themselves as the approver for any access package that uses this dynamic approval policy. Or worse: rewrite the workflow to return "auto-approve" for a specific requestor and never invoke a human at all.&lt;/p&gt;

&lt;p&gt;Most of the Function App blast-radius list applies here too, with one extra wrinkle. A Logic App often has fewer source-code artifacts (the workflow can be authored directly in the portal, although &lt;a href="https://learn.microsoft.com/en-us/azure/logic-apps/create-standard-workflows-visual-studio-code" rel="noopener noreferrer"&gt;authoring it through a code project under source control&lt;/a&gt; is strongly recommended for exactly the reasons we are discussing). What a Logic App always has, in abundance, is &lt;em&gt;connections&lt;/em&gt;. The Microsoft Graph connection, the SharePoint connection, the HTTP connector pointing at the change-management API, the Salesforce connection for project data. Each connection is a credential the Logic App will use, and each credential is something an attacker would happily inherit if they took ownership of the Logic App resource. The connection objects themselves are Azure resources in the same resource group, subject to the same RBAC inheritance chain.&lt;/p&gt;

&lt;p&gt;The surface is broader than the Function App's. The principle is the same. The Logic App is Control Plane because of who calls it and what its answer is used for.&lt;/p&gt;

&lt;h2&gt;
  
  
  The credential ladder
&lt;/h2&gt;

&lt;p&gt;Both examples force the same question. Once your code is running, what does it use to authenticate when it calls downstream? Microsoft Graph, the corporate HR system, a SharePoint list, an external SaaS API. Pick one. There are three honest answers, and they are not equivalent.&lt;/p&gt;

&lt;h3&gt;
  
  
  Static keys: assume already compromised
&lt;/h3&gt;

&lt;p&gt;A static API key, or the symmetric &lt;code&gt;client_id&lt;/code&gt; plus &lt;code&gt;client_secret&lt;/code&gt; pair, is the least secure credential you can use, and you should treat it as already compromised. That is not hyperbole and it is not a "best practice" framing. It is the structural reality.&lt;/p&gt;

&lt;p&gt;You did not pick the entropy. You do not know who pasted it into a chat window during an incident three years ago. You do not know whether the developer who first received it stored a copy in their personal note-taking app. You do not know whether the CI variable holding it was visible to the build agent's logs at some point during a regression. You do not know whether the rotation calendar reminder fired, and you do not know whether the previous value was scrubbed from every place it was ever stored.&lt;/p&gt;

&lt;p&gt;For non-critical workloads, this is a manageable risk. For code that decides what claims your applications see, or who approves access to your most sensitive packages, it is not. If your Control Plane Entra extensions are still authenticating outbound with static symmetric credentials, the rest of your Zero Trust program is theater for that path.&lt;/p&gt;

&lt;h3&gt;
  
  
  Certificates: better, but only as strong as their issuance and storage
&lt;/h3&gt;

&lt;p&gt;Certificate-based authentication is genuinely a step up. The credential is asymmetric, the private key never has to leave the host that uses it, and the rotation story is cleaner. The honest assessment, though, is that a certificate is only as secure as the process that produces, transports and stores it.&lt;/p&gt;

&lt;p&gt;Be specific about the questions a certificate forces you to answer. How was the certificate issued: by a managed PKI, by your tenant's own CA, by a developer with &lt;code&gt;openssl&lt;/code&gt; at a laptop? How did the public key end up in the Entra app registration credentials: a portal upload by a human, a pipeline call to Microsoft Graph, a manual step in a runbook? Where is the private key stored: on the host filesystem, in an HSM, in a Key Vault? If it is in Key Vault, is it &lt;a href="https://learn.microsoft.com/en-us/azure/key-vault/certificates/about-certificates" rel="noopener noreferrer"&gt;non-exportable&lt;/a&gt;, or did someone tick the wrong box when they imported it? Was the certificate generated &lt;em&gt;inside&lt;/em&gt; the Key Vault, or was it generated elsewhere and imported, in which case the original private key existed somewhere outside the HSM at least once?&lt;/p&gt;

&lt;p&gt;And then the question Part 1 of this series spent an entire section on: who has Owner on that Key Vault? Walk the &lt;a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/overview" rel="noopener noreferrer"&gt;Azure RBAC inheritance chain&lt;/a&gt; from the Key Vault up through its resource group, its subscription, its management groups, and the root. Every Owner along that chain is, for practical purposes, an Owner of every certificate in that Key Vault. The certificate is not as static as a symmetric key, but the principals who can extract or replace it follow the same inheritance rules every other Azure resource follows.&lt;/p&gt;

&lt;p&gt;A certificate in a Key Vault inside a wide-RBAC subscription is, operationally, not very different from a static key with extra steps. The credential improved. The principal model around it did not.&lt;/p&gt;

&lt;h3&gt;
  
  
  Managed Identities: the right baseline
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview" rel="noopener noreferrer"&gt;managed identity&lt;/a&gt; is the right baseline for any Entra extension hosted on Azure compute. The platform manages the credential. The compute resource (Function App, Logic App, App Service, container) requests an access token from a local metadata endpoint. Entra issues it. No human, no pipeline, no developer ever sees a secret.&lt;/p&gt;

&lt;p&gt;When the call has to leave Azure, &lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;Workload Identity Federation&lt;/a&gt; extends the same pattern across trust boundaries. A managed identity can act as the credential for an Entra app registration through a federated identity credential, and an Entra-issued token can be exchanged for an external system's token as long as that system supports the &lt;a href="https://www.rfc-editor.org/rfc/rfc7523" rel="noopener noreferrer"&gt;OpenID Connect / RFC 7523&lt;/a&gt; pattern. The longer treatment of how this composes for AI agent platforms is in &lt;a href="https://dev.to/astaykov/your-agentic-platform-doesnt-need-a-single-secret-managed-identities-workload-identity-3602"&gt;Your agentic platform doesn't need a single secret&lt;/a&gt;, and most of the same building blocks apply when the workload happens to be an Entra extension instead of an agent runtime.&lt;/p&gt;

&lt;p&gt;Even the right answer comes with its own discipline. A managed identity is not a free pass. Two specific guardrails matter:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;System-assigned by default.&lt;/strong&gt; A system-assigned managed identity is lifecycle-bound to a single Azure resource. Delete the resource, the identity is gone. Replace the resource, the new instance gets a brand-new identity that must be re-granted permissions. That friction is the whole point. It makes the credential and the resource indistinguishable, which is exactly what you want for a Control Plane workload.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;User-assigned managed identities are subscription-scoped reusable identities.&lt;/strong&gt; A user-assigned MI lives in a resource group and can be attached to any number of resources in the same tenant. That convenience is also a risk. Any Contributor on a resource in the same subscription can attach the same managed identity to a &lt;em&gt;new&lt;/em&gt; resource of their choosing, and that new resource can now act as the privileged caller into Microsoft Graph. Treat user-assigned MIs that hold real downstream permissions as Control Plane objects of their own, place them in the same dedicated Control Plane subscription as the workloads that use them, and never share one across planes.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When a managed identity is placed correctly, on a Control Plane resource, in a Control Plane subscription, under a Control Plane management group, the answer to "who can use this credential against Microsoft Graph" collapses to "exactly the workloads I deployed there, and exactly the principals who have rights to deploy to that subscription." That is the credential model these extensions deserve.&lt;/p&gt;

&lt;h2&gt;
  
  
  Putting the two halves together
&lt;/h2&gt;

&lt;p&gt;Part 1 of this series argued that Entra extensions belong on the Control Plane because of what they decide. This article argued that the security of those decisions is the security of the smallest detail underneath them: the source repository, the pipeline identity, the runtime principal model, the credential the runtime uses to call out, the Key Vault that backs it, the RBAC inheritance above all of them.&lt;/p&gt;

&lt;p&gt;There is a uniform answer to the placement question. The Function App, the Logic App, the Key Vault, the user-assigned managed identities, the connections, the API connectors all belong in a dedicated Control Plane subscription under a dedicated Control Plane management group, directly under the root. Not in the platform identity subscription, not in a shared services subscription, not in an application landing zone. The list of principals with Contributor or Owner anywhere on the chain above those resources has to be short, audited, and reviewed on a cadence shorter than your access-review cycle.&lt;/p&gt;

&lt;p&gt;There is a uniform answer to the credential question too. The default credential for a Control Plane Entra extension is a managed identity, scoped tightly, lifecycle-bound where possible, system-assigned where possible, with &lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;federated identity credentials&lt;/a&gt; covering the cases where the call has to leave Azure. Certificates are a tolerated middle step when the call truly has nowhere else to go. Static symmetric keys are not an option for code that influences Entra decisions, full stop.&lt;/p&gt;

&lt;p&gt;Use the extensibility. It is what makes Entra a great product, and the next year will keep adding new hooks worth using. Just host the code behind those hooks the same way you host the things Entra is protecting. The point of a Zero Trust enforcement engine is undermined the moment its decisions are shaped by code anyone can quietly replace.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/custom-extension-overview" rel="noopener noreferrer"&gt;Custom authentication extensions overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-logic-apps-integration" rel="noopener noreferrer"&gt;Entitlement management Logic Apps integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-dynamic-approval" rel="noopener noreferrer"&gt;Externally determine the approval requirements for an access package using custom extensions (dynamic approval)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/managed-identities-azure-resources/overview" rel="noopener noreferrer"&gt;Managed identities for Azure resources overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/workload-id/workload-identity-federation" rel="noopener noreferrer"&gt;Workload Identity Federation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/key-vault/certificates/about-certificates" rel="noopener noreferrer"&gt;About Azure Key Vault certificates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/overview" rel="noopener noreferrer"&gt;Azure RBAC overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/logic-apps/create-standard-workflows-visual-studio-code" rel="noopener noreferrer"&gt;Create a Standard logic app workflow with Visual Studio Code (source-controlled authoring)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7523" rel="noopener noreferrer"&gt;RFC 7523: JWT Profile for OAuth 2.0 Client Authentication and Authorization Grants&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/security/privileged-access-workstations/privileged-access-access-model" rel="noopener noreferrer"&gt;Enterprise Access Model (Control Plane / Management Plane)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>microsoftentra</category>
      <category>managedidentity</category>
      <category>identitygovernance</category>
      <category>zerotrust</category>
    </item>
    <item>
      <title>Microsoft Entra extensibility is a gift. It is also Control Plane.</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 30 Jun 2026 09:17:00 +0000</pubDate>
      <link>https://dev.to/astaykov/microsoft-entra-extensibility-is-a-gift-it-is-also-control-plane-4egn</link>
      <guid>https://dev.to/astaykov/microsoft-entra-extensibility-is-a-gift-it-is-also-control-plane-4egn</guid>
      <description>&lt;p&gt;Microsoft Entra is the Zero Trust policy enforcement engine sitting at the intersection of identities, endpoints, AI agents, networks and data. Every conversation about Conditional Access, every Privileged Identity Management workflow, every access package review, every token issued to an application or an agent: it all funnels through Entra. That is the entire point of the product, and that is why so much of the security industry's energy is spent hardening it.&lt;/p&gt;

&lt;p&gt;The most interesting thing happening to Entra right now is not a new policy type. It is &lt;em&gt;extensibility&lt;/em&gt;. In the last few years Entra has quietly become one of the most extensible identity platforms in the enterprise market. &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/custom-extension-overview" rel="noopener noreferrer"&gt;Custom authentication extensions&lt;/a&gt; let your code shape token issuance and authentication events. &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-external-method-manage" rel="noopener noreferrer"&gt;External authentication methods&lt;/a&gt; plug third-party MFA providers into the sign-in pipeline. &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/privileged-identity-management-custom-extensions" rel="noopener noreferrer"&gt;PIM custom extensions&lt;/a&gt; let your business logic approve or deny privileged role activation. &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/lifecycle-workflow-extensibility" rel="noopener noreferrer"&gt;Lifecycle workflow custom task extensions&lt;/a&gt; let your code influence whether an employee account is enabled, disabled, or transformed. Entitlement management talks to &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-logic-apps-integration" rel="noopener noreferrer"&gt;Logic Apps for governance workflows&lt;/a&gt;, and a specific variant, &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-dynamic-approval" rel="noopener noreferrer"&gt;dynamic approval&lt;/a&gt;, lets your code decide &lt;em&gt;who&lt;/em&gt; approves an access package request, or whether the request should be approved at all. More extension points are coming. The trend line is obvious.&lt;/p&gt;

&lt;p&gt;This is genuinely good. Identity decisions are business decisions, and business logic belongs in code your business writes. Extensibility is what turns Entra from a fixed product into a programmable enforcement engine, and that is the right direction.&lt;/p&gt;

&lt;p&gt;It is also the part where the security conversation almost always stops.&lt;/p&gt;

&lt;h2&gt;
  
  
  The reframe nobody wants to write
&lt;/h2&gt;

&lt;p&gt;Every extension point hands a piece of the Entra trust boundary to a system Entra does not own. The decision is still announced from Entra. The &lt;em&gt;signal&lt;/em&gt; that drives the decision is shaped somewhere else. That somewhere else is now Control Plane, in the language of Microsoft's current &lt;a href="https://learn.microsoft.com/en-us/security/privileged-access-workstations/privileged-access-access-model" rel="noopener noreferrer"&gt;Enterprise Access Model&lt;/a&gt; (what the legacy tiered administration model used to call Tier 0). Control Plane is the set of systems that grant access to everything else: the directory itself, the privileged groups inside it, and now, by extension, any code that shapes a directory decision at runtime.&lt;/p&gt;

&lt;p&gt;Sit with the implications for a minute, scenario by scenario:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/privileged-identity-management-custom-extensions" rel="noopener noreferrer"&gt;PIM custom extension&lt;/a&gt; can influence whether an identity gets a high-privilege role activated, in milliseconds, with no human in the loop. The endpoint that responds to PIM is now part of your privileged-access path.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/lifecycle-workflow-extensibility" rel="noopener noreferrer"&gt;lifecycle workflow custom task extension&lt;/a&gt; can be the anchor that decides whether a leaver's account is actually disabled tomorrow morning. The code that answers that workflow call is now part of your joiner-mover-leaver process.&lt;/li&gt;
&lt;li&gt;A &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-dynamic-approval" rel="noopener noreferrer"&gt;Logic App driving dynamic approval&lt;/a&gt; can decide who is allowed to approve a request for a sensitive access package, or whether the request gets pre-approved with no human reviewer at all. The Logic App is now part of your access governance.&lt;/li&gt;
&lt;li&gt;An Azure Function backing a &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/custom-extension-overview" rel="noopener noreferrer"&gt;custom claims provider&lt;/a&gt; can inject custom claims into the access tokens your applications consume. The Function is now part of your token issuance.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Every one of those endpoints is a Control Plane system. Not "important." Not "in scope for the next pentest." Control Plane. The same category you reserve for your domain controllers, your Entra Connect server, your root CA, and the accounts that can write to any of them.&lt;/p&gt;

&lt;p&gt;Most security writing about Entra extensibility focuses on the Entra side: how to register the extension, what scopes it needs, how the audit log captures the call. That work is necessary and well covered. The Azure side, where the actual code runs, is barely covered at all. That is the gap I want to close.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Cloud Adoption Framework already saw half of this
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/overview" rel="noopener noreferrer"&gt;Azure Cloud Adoption Framework&lt;/a&gt; and the &lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/landing-zone/" rel="noopener noreferrer"&gt;Azure landing zones&lt;/a&gt; reference architecture do something deliberate: they split &lt;em&gt;platform&lt;/em&gt; landing zones from &lt;em&gt;application&lt;/em&gt; landing zones, and inside platform they carve out a dedicated &lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access" rel="noopener noreferrer"&gt;identity subscription&lt;/a&gt; for identity-related infrastructure. The &lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/landing-zone/design-area/resource-org-subscriptions" rel="noopener noreferrer"&gt;subscription organization guidance&lt;/a&gt; treats identity as its own thing, not as a workload that shares a subscription with marketing analytics.&lt;/p&gt;

&lt;p&gt;That is exactly the right instinct. The problem is that the identity subscription is almost always described as the home of legacy identity infrastructure: Active Directory Domain Services domain controllers, Entra Connect, a Privileged Access Workstation jump box, maybe a few related VMs. It is described as the place where the &lt;em&gt;identity infrastructure that runs in Azure&lt;/em&gt; lives.&lt;/p&gt;

&lt;p&gt;It is not yet described as the place where the &lt;em&gt;Azure compute that influences Entra decisions at runtime&lt;/em&gt; lives. That is a different category, and it did not really exist five years ago in the volume it exists now. A landing zone designed in 2021 was not built to host the Function App behind your custom claims provider, the Logic App behind your dynamic approval policy, the queue-triggered Function that decides whether a lifecycle workflow disables an account. None of that was a workload pattern then.&lt;/p&gt;

&lt;p&gt;It is now. And those workloads are at least as sensitive as Entra Connect, because they execute on every relevant identity event, and a successful tamper does not generate the noisy reconfiguration footprint that touching Entra Connect would generate. They are quieter and at least as powerful.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Azure RBAC inheritance chain is the trap
&lt;/h2&gt;

&lt;p&gt;Here is the part of the picture that does not get said out loud enough. &lt;a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/overview" rel="noopener noreferrer"&gt;Azure role-based access control&lt;/a&gt; inherits top-down, and the inheritance is unbreakable. A role assignment at a resource, resource group, subscription, &lt;a href="https://learn.microsoft.com/en-us/azure/governance/management-groups/overview" rel="noopener noreferrer"&gt;management group&lt;/a&gt;, or the tenant root management group flows downward to every resource underneath. Contributor at the resource group flows to the resource. Contributor at the subscription flows to every resource group in it. Contributor at the management group flows to every subscription. Contributor at the root management group flows to &lt;em&gt;everything&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;This is fine for most workloads.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;It is corrosive for workloads that influence Entra decisions.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That sentence is the entire argument of this article. Read it once more. The same RBAC model that makes Azure pleasant to operate at scale is the model that lets a Contributor four management groups above your Function App quietly become the author of the claims your applications trust. There is no Entra-side hardening that fixes this. There is no Conditional Access policy that fixes this. The fix is structural, and it lives in Azure.&lt;/p&gt;

&lt;p&gt;Picture an organization with a moderately mature Azure footprint. There is a platform management group, an application management group, a couple of regional management groups, a Sandbox management group, a Production management group. The custom claims provider Function App lives in a shared "platform services" subscription because that is where the platform team puts platform things. Now ask the inheritance question: who is Contributor on that subscription? Who is Contributor on its parent management group? Who is Contributor on the root? In most organizations the answer is "more people than you think, and at least one service principal you forgot about." Any one of them can replace the deployed code on that Function App. They do not need to touch the source repository. They do not need to break the CI/CD pipeline. They just deploy a new ZIP. The next time Entra calls your custom claims provider, it is calling their code.&lt;/p&gt;

&lt;p&gt;Source-control hardening is necessary and frequently celebrated. It is also not sufficient on its own, because Entra does not invoke your repository. Entra invokes the runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Where these resources actually belong
&lt;/h2&gt;

&lt;p&gt;My direct take. The Azure resources that host code which influences Entra decisions are Control Plane resources. They belong either:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;directly under the &lt;strong&gt;root management group&lt;/strong&gt;, in a dedicated subscription that exists for exactly this purpose, or&lt;/li&gt;
&lt;li&gt;one level deeper, under a dedicated &lt;strong&gt;Control Plane management group&lt;/strong&gt; that itself sits directly under the root.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Not deeper. Not in the platform identity subscription where your domain controllers live. Not in a shared services subscription. Not in an application landing zone, ever. The blast radius of "any Contributor anywhere on the inheritance path can swap the runtime" forces the resources up the tree, into a place where the list of principals with write rights is short, audited, and reviewed.&lt;/p&gt;

&lt;p&gt;That choice has follow-on consequences. Resources at the top of the hierarchy do not benefit from the policies and guardrails the landing zone reference architecture applies further down. The dedicated Control Plane management group needs its own policy set: deny-by-default network rules, mandatory diagnostic settings to a tenant-level log analytics workspace, deny-create policies for everything except the resource types you actually need, and Resource Locks at the resource group level that survive the people who set them. Pinning these things at the top of the tree means designing them at the top of the tree.&lt;/p&gt;

&lt;h2&gt;
  
  
  The questions you must be able to answer
&lt;/h2&gt;

&lt;p&gt;Before any Entra extension goes into production, the team that owns it has to be able to answer each of these without hedging:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Who controls the source code? Specifically, who has push rights on the protected branch, who can bypass branch protection, and who can approve a pull request?&lt;/li&gt;
&lt;li&gt;Who controls the CI/CD pipeline? Who can edit the pipeline definition? Who can re-run a failed deployment with a different artifact?&lt;/li&gt;
&lt;li&gt;What identity does the pipeline run as when it deploys to Azure? If it is a service principal with a client secret or a stored certificate, treat that credential as already known to the threat actor.&lt;/li&gt;
&lt;li&gt;Who can directly replace the runtime, bypassing source and pipeline entirely? Walk the full RBAC inheritance chain from the resource up to the root, including every group, service principal, and managed identity with Contributor, Owner, or User Access Administrator.&lt;/li&gt;
&lt;li&gt;Where do the credentials this extension uses to call downstream systems live? Who has Owner on that Key Vault? Walk the inheritance chain again.&lt;/li&gt;
&lt;li&gt;Does the extension run with a system-assigned managed identity, a user-assigned managed identity shared with other workloads, a certificate in a Key Vault, or a client secret? Each answer has a different blast radius.&lt;/li&gt;
&lt;li&gt;How fast can you detect that the deployed code on the Function App or the deployed logic on the Logic App has changed? How fast can you detect that a role assignment was created on the hosting subscription or one of its parents?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If any of those questions is uncomfortable, the extension is not yet ready to be invoked by Entra in production.&lt;/p&gt;

&lt;h2&gt;
  
  
  What comes next
&lt;/h2&gt;

&lt;p&gt;The two scenarios I want to walk in detail are a &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/custom-extension-overview" rel="noopener noreferrer"&gt;custom claims provider&lt;/a&gt; backed by an Azure Function and a &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-dynamic-approval" rel="noopener noreferrer"&gt;dynamic approval Logic App&lt;/a&gt; backing an entitlement management access package. Both are concrete. Both are increasingly common. Both expose the entire chain: source repository, pipeline identity, runtime resource, hosting subscription, RBAC inheritance, downstream credentials, Key Vault, the lot.&lt;/p&gt;

&lt;p&gt;And both lead into a question that deserves a fair, opinionated answer: what credential should that code use when it has to call back into Microsoft Graph or out to an external system? Static API key, certificate, or managed identity? Each option has a real failure mode and a real guardrail. The right answer is rarely the convenient one.&lt;/p&gt;

&lt;p&gt;That is the subject of the next article in this series. Where Part 1 makes the case that Entra extensibility is Control Plane, Part 2 will walk the two examples end to end and grade the credential ladder beneath them. Watch this space.&lt;/p&gt;

&lt;p&gt;The goal of this series is not to talk anyone out of using Entra extensibility. The opposite. Use it. It is what makes Entra a great product. Just host the code that influences Entra decisions the same way you host the things Entra is protecting.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/custom-extension-overview" rel="noopener noreferrer"&gt;Custom authentication extensions overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-external-method-manage" rel="noopener noreferrer"&gt;Manage an external authentication method in Microsoft Entra ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/privileged-identity-management/privileged-identity-management-custom-extensions" rel="noopener noreferrer"&gt;PIM custom extensions for role activation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/lifecycle-workflow-extensibility" rel="noopener noreferrer"&gt;Lifecycle workflow extensibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-logic-apps-integration" rel="noopener noreferrer"&gt;Entitlement management Logic Apps integration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-dynamic-approval" rel="noopener noreferrer"&gt;Externally determine approval requirements for an access package using custom extensions (dynamic approval)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/overview" rel="noopener noreferrer"&gt;Azure Cloud Adoption Framework overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/landing-zone/" rel="noopener noreferrer"&gt;Azure landing zones&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/landing-zone/design-area/resource-org-subscriptions" rel="noopener noreferrer"&gt;Landing zone subscription organization and governance&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/cloud-adoption-framework/ready/landing-zone/design-area/identity-access" rel="noopener noreferrer"&gt;Landing zone identity and access management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/role-based-access-control/overview" rel="noopener noreferrer"&gt;Azure RBAC overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/governance/management-groups/overview" rel="noopener noreferrer"&gt;Azure management groups&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/security/privileged-access-workstations/privileged-access-access-model" rel="noopener noreferrer"&gt;Enterprise Access Model (Control Plane / Management Plane)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>microsoftentra</category>
      <category>zerotrust</category>
      <category>identitygovernance</category>
      <category>cloudadoptionframework</category>
    </item>
    <item>
      <title>One identity, two clouds: an AWS-hosted agent that authorizes with Microsoft Entra Agent ID</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 23 Jun 2026 09:23:00 +0000</pubDate>
      <link>https://dev.to/astaykov/one-identity-two-clouds-an-aws-hosted-agent-that-authorizes-with-microsoft-entra-agent-id-2bkl</link>
      <guid>https://dev.to/astaykov/one-identity-two-clouds-an-aws-hosted-agent-that-authorizes-with-microsoft-entra-agent-id-2bkl</guid>
      <description>&lt;p&gt;There is a quiet assumption baked into most identity conversations, and it usually goes unstated because everyone in the room already believes it: Microsoft identity is for Microsoft resources, and you run it on Microsoft infrastructure. Both halves feel so obvious that nobody bothers to test them.&lt;/p&gt;

&lt;p&gt;So I tested them. I built a proof-of-concept where an AI agent runs entirely on AWS, holds its own Microsoft Entra Agent ID, and uses that single identity to authorize calls to resources on &lt;em&gt;both&lt;/em&gt; sides of the cloud boundary: a Microsoft service and an AWS Lambda. The agent never touches Azure compute. The Lambda never sees an AWS credential on the call. And nothing about it required a special bridge, because the bridge already exists and it is called OAuth.&lt;/p&gt;

&lt;p&gt;The sample is &lt;a href="https://github.com/astaykov/agentid-agentcore" rel="noopener noreferrer"&gt;astaykov/agentid-agentcore&lt;/a&gt;. This is the walkthrough of why it works, written for the architect whose first reaction to "Microsoft Entra in my AWS stack" is a raised eyebrow.&lt;/p&gt;

&lt;h2&gt;
  
  
  The shape of it, stated plainly
&lt;/h2&gt;

&lt;p&gt;A browser single-page app signs the user in with &lt;a href="https://learn.microsoft.com/entra/identity-platform/msal-overview" rel="noopener noreferrer"&gt;MSAL.js&lt;/a&gt; and hands the resulting access token to an agent. The agent is hosted on &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html" rel="noopener noreferrer"&gt;AWS Bedrock AgentCore Managed Runtime&lt;/a&gt;, a fully managed runtime for agents. Inside that runtime, the agent acts &lt;em&gt;on behalf of the signed-in user&lt;/em&gt; against two downstream resources:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an &lt;strong&gt;AWS Lambda&lt;/strong&gt;, fronted by an API Gateway HTTP API (the PoC calls it the Echo API), and&lt;/li&gt;
&lt;li&gt;the &lt;strong&gt;&lt;a href="https://learn.microsoft.com/graph/mcp-server/get-started" rel="noopener noreferrer"&gt;Microsoft MCP Server for Enterprise&lt;/a&gt;&lt;/strong&gt;, the Microsoft-hosted tool surface over Microsoft Graph.
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;User
  ↓  sign-in (MSAL.js)
Browser SPA
  ↓  Entra access token  [Bearer, header-only]
AgentCore Managed Runtime  ── hosted on AWS, holds an Entra Agent ID
  ├─→  AWS Lambda  (API Gateway HTTP API, native JWT authorizer)
  └─→  Microsoft MCP server
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Read that topology again and notice what is &lt;em&gt;not&lt;/em&gt; in it. There is no Azure virtual machine, no Azure Container App, no Azure Function. The compute is 100% AWS. The identity is 100% Microsoft Entra. Those two facts are usually assumed to travel together. They do not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The AWS resource is the whole point
&lt;/h2&gt;

&lt;p&gt;It would be an unremarkable demo if the agent only called Microsoft services. Of course a Microsoft identity opens Microsoft doors. The claim that actually needs proving is the other one: that a Microsoft Entra-issued token can open an &lt;em&gt;AWS&lt;/em&gt; door.&lt;/p&gt;

&lt;p&gt;So look closely at the Echo API. It is an &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop.html" rel="noopener noreferrer"&gt;AWS API Gateway v2 HTTP API&lt;/a&gt; in front of a Lambda function. Nothing about it is Microsoft. And its access control is a &lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html" rel="noopener noreferrer"&gt;native API Gateway JWT authorizer&lt;/a&gt;, the AWS-built-in kind, configured with exactly two things:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an &lt;strong&gt;issuer&lt;/strong&gt; of &lt;code&gt;https://login.microsoftonline.com/{tenant}/v2.0&lt;/code&gt;, and&lt;/li&gt;
&lt;li&gt;an &lt;strong&gt;audience&lt;/strong&gt; equal to the Echo API's own app registration.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is the entire trust configuration. An AWS resource, gated by an AWS-native authorizer, that grants access only when presented with a token &lt;em&gt;minted by Microsoft Entra&lt;/em&gt; and bound to the signed-in user. No Cognito. No IAM SigV4 signing on that request. No custom Lambda authorizer doing bespoke validation. The AWS platform validates a Microsoft token because, to an OIDC-aware authorizer, Microsoft Entra is simply another standards-compliant issuer.&lt;/p&gt;

&lt;p&gt;This is the sentence I want the skeptical AWS architect to sit with: &lt;em&gt;your API Gateway already knows how to trust Microsoft Entra. You have not been told to think of it that way, but the capability has been there all along.&lt;/em&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Same identity, the Microsoft side
&lt;/h2&gt;

&lt;p&gt;The same agent, carrying the same identity, also calls the Microsoft MCP server. One agent. One Entra Agent ID. Two destinations in two different clouds, each handed a token scoped to it, each validating that token on its own terms.&lt;/p&gt;

&lt;p&gt;This is what makes Entra Agent ID interesting as an identity layer rather than a Microsoft-only convenience. The agent is not "an AWS thing that occasionally phones Microsoft." It is a governed identity in Microsoft Entra (&lt;a href="https://learn.microsoft.com/entra/agent-id/what-is-microsoft-entra-agent-id" rel="noopener noreferrer"&gt;an agent identity created from a blueprint&lt;/a&gt;) that happens to be hosted on AWS, and it reaches whatever it is authorized to reach regardless of which cloud the resource lives in. Microsoft's own documentation is explicit that &lt;a href="https://learn.microsoft.com/entra/agent-id/what-is-microsoft-entra-agent-id" rel="noopener noreferrer"&gt;Entra Agent ID works with agents built on non-Microsoft platforms, AWS Bedrock among them&lt;/a&gt;. The PoC is just that statement made concrete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this should land for an AWS architect
&lt;/h2&gt;

&lt;p&gt;The instinct to distrust cross-vendor identity is healthy, because most of it historically meant proprietary glue: a connector, an SDK shim, a sync job that copied identities from one directory into another and drifted out of band within a quarter. None of that is happening here.&lt;/p&gt;

&lt;p&gt;What is happening is plain OAuth 2.0 and OpenID Connect:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;✅ The AWS authorizer trusts an &lt;strong&gt;OIDC issuer&lt;/strong&gt;. Entra publishes standard OIDC discovery metadata; the authorizer validates &lt;code&gt;iss&lt;/code&gt;, &lt;code&gt;aud&lt;/code&gt;, and signature against it. This is the same mechanism you would use to trust any external IdP.&lt;/li&gt;
&lt;li&gt;✅ The agent acquires &lt;strong&gt;resource-scoped, user-bound tokens&lt;/strong&gt; through a standard &lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow" rel="noopener noreferrer"&gt;on-behalf-of style exchange&lt;/a&gt;. The user's sign-in is turned into a token audienced to each specific downstream resource before the call is made.&lt;/li&gt;
&lt;li&gt;⚙️ The agent runtime does use &lt;strong&gt;&lt;a href="https://learn.microsoft.com/entra/msal/python/" rel="noopener noreferrer"&gt;MSAL Python&lt;/a&gt;&lt;/strong&gt; to perform the exchange. That is not a proprietary bridge between the clouds; it is Microsoft's standards-based authentication library, the same one you would reach for to speak OAuth and OIDC to Entra from any Python application. The wire protocol is still OAuth and OIDC. MSAL is simply the most direct way to speak it from the agent.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you already federate your AWS workloads to Microsoft Entra ID for human sign-in, this is the next step: it is time for your AI agents to get their identity from there too.&lt;/p&gt;

&lt;h2&gt;
  
  
  The token exchange, kept in its lane
&lt;/h2&gt;

&lt;p&gt;There is real machinery behind "acts on behalf of the user," and it is worth one honest paragraph rather than a victory lap of acronyms. Before each downstream call, the agent converts the inbound user token into a token scoped to the specific target resource, in-process, using &lt;a href="https://learn.microsoft.com/entra/msal/python/" rel="noopener noreferrer"&gt;MSAL Python&lt;/a&gt;. The result is a token whose audience is the AWS Lambda for the Lambda call, and the MCP server for the MCP call, with the original user identity preserved throughout. The two-stage exchange that Entra Agent ID uses to do this is &lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow" rel="noopener noreferrer"&gt;documented here&lt;/a&gt;; it is the plumbing, not the headline. The headline is that the user's delegated context survives the entire trip, across clouds, to the downstream resources. And this chain can be infinite hops long.&lt;/p&gt;

&lt;h2&gt;
  
  
  The security posture, briefly
&lt;/h2&gt;

&lt;p&gt;A few properties matter more than the rest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;🔐 The agent never hands a credential to the model. Tools return only their response payloads; the inbound token lives in a tightly scoped variable that is cleared in a &lt;code&gt;finally&lt;/code&gt; block after every invocation and never reaches the LLM.&lt;/li&gt;
&lt;li&gt;🔍 Tokens are never logged. Only non-secret routing claims (issuer, audience, app id, scope, and the user's object id as a cache key) are emitted, never a token, header, or signature.&lt;/li&gt;
&lt;li&gt;The blueprint credential is read once per process and dropped from memory immediately after the client is constructed.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the honest production caveat, because a PoC that hides its rough edges is selling something: this sample authenticates the agent's blueprint with a client secret, which is fine for a demo and &lt;a href="https://learn.microsoft.com/entra/agent-id/best-practices-agent-id" rel="noopener noreferrer"&gt;explicitly not recommended for production&lt;/a&gt;. The production path is a certificate or, better, a &lt;a href="https://learn.microsoft.com/entra/agent-id/create-blueprint#configure-credentials-for-the-agent-identity-blueprint" rel="noopener noreferrer"&gt;managed identity used as a federated credential&lt;/a&gt; so there is no stored secret at all. I have made &lt;a href="https://dev.to/astaykov/your-agentic-platform-doesnt-need-a-single-secret-managed-identities-workload-identity-3602"&gt;the broader case for a secret-less agentic platform before&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  What's next, and the thesis
&lt;/h2&gt;

&lt;p&gt;The current PoC still keeps one secret on the AWS side: the blueprint credential. The next step closes that gap in the most fitting way possible: register AWS's own STS as a federated identity credential the Entra blueprint trusts, and let AWS issue the token that proves the agent's identity to Microsoft. At that point the loop is closed in both directions. AWS resources trust Microsoft-issued tokens, and Microsoft trusts AWS-issued tokens, with no shared secret anywhere between them.&lt;/p&gt;

&lt;p&gt;That is the whole thesis, and it is smaller than it sounds: a resource trusts a token, not a cloud. The delegation semantics that have governed &lt;a href="https://dev.to/astaykov/from-obo-apis-to-agent-identities-entra-conditional-access-still-works-the-same-140c"&gt;middle-tier APIs for years&lt;/a&gt; did not change when the middle tier became an AI agent, and they do not change when that agent moves to AWS. An identity is a protocol boundary. If your Lambda can trust Microsoft Entra Agent ID, your agent can run anywhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  References
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://github.com/astaykov/agentid-agentcore" rel="noopener noreferrer"&gt;astaykov/agentid-agentcore (the PoC)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/what-is-microsoft-entra-agent-id" rel="noopener noreferrer"&gt;What is Microsoft Entra Agent ID?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow" rel="noopener noreferrer"&gt;Authenticate and acquire tokens for autonomous agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/best-practices-agent-id" rel="noopener noreferrer"&gt;Best practices for Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/agent-id/create-blueprint" rel="noopener noreferrer"&gt;Create an agent identity blueprint&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow" rel="noopener noreferrer"&gt;Microsoft identity platform and OAuth 2.0 On-Behalf-Of flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/msal/python/" rel="noopener noreferrer"&gt;MSAL for Python&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/msal-overview" rel="noopener noreferrer"&gt;MSAL overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/mcp-server/get-started" rel="noopener noreferrer"&gt;Get started with the Microsoft MCP Server for Enterprise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html" rel="noopener noreferrer"&gt;AWS Bedrock AgentCore Runtime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/identity.html" rel="noopener noreferrer"&gt;AWS Bedrock AgentCore Identity&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-jwt-authorizer.html" rel="noopener noreferrer"&gt;Control access to HTTP APIs with JWT authorizers (API Gateway)&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/apigateway/latest/developerguide/http-api-develop.html" rel="noopener noreferrer"&gt;Develop HTTP APIs in API Gateway&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>entraagentid</category>
      <category>aws</category>
      <category>bedrockagentcore</category>
      <category>aiagents</category>
    </item>
  </channel>
</rss>
