<?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%2F3cee6030-a0e4-4b4f-9194-400f671a4957.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>The AI agent kill switch is a procedure, not a feature</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Thu, 15 Oct 2026 10:53:00 +0000</pubDate>
      <link>https://dev.to/astaykov/the-ai-agent-kill-switch-is-a-procedure-not-a-feature-308j</link>
      <guid>https://dev.to/astaykov/the-ai-agent-kill-switch-is-a-procedure-not-a-feature-308j</guid>
      <description>&lt;p&gt;An invoice-processing agent starts submitting the same job repeatedly. You disable its principal. Even if Microsoft Graph now rejects its requests, the agent may still use a provider key, write export files or leave accepted jobs running.&lt;/p&gt;

&lt;p&gt;Which effects did the kill switch actually stop? The credential operation may have succeeded without containing the workload.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;An AI agent kill switch is an operational procedure with a testable outcome.&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The hypothetical invoice agent has four action paths: Graph access, model access, local writes and submitted jobs. Following those paths exposes the architectural obligation: someone outside the agent must retain the authority and information needed to stop its consequential activity.&lt;/p&gt;

&lt;h2&gt;
  
  
  The stop objective determines the control
&lt;/h2&gt;

&lt;p&gt;"Stop" needs a defined outcome. &lt;em&gt;Containment&lt;/em&gt; denies specified consequential actions; &lt;em&gt;quiescence&lt;/em&gt; additionally requires relevant execution and outstanding work to reach verified terminal states. A paused or queued job is not terminal, and completed work may still have produced effects requiring recovery.&lt;/p&gt;

&lt;p&gt;The invoice incident requires both: no further submissions or export writes, no remaining agent execution and no unresolved processing jobs. A lower-risk incident might permit isolated computation to continue while denying every forbidden effect.&lt;/p&gt;

&lt;p&gt;Three artifacts make that objective reviewable. The &lt;em&gt;stop scope&lt;/em&gt; identifies effects, execution units and permitted collateral impact. The &lt;em&gt;shutdown protocol&lt;/em&gt; assigns independent authority, dependencies and failure handling. The &lt;em&gt;acceptance report&lt;/em&gt; records observed coverage, timing and persistence.&lt;/p&gt;

&lt;p&gt;Build the scope from credentials, reachable destinations, writable resources and submitted work. "All registered paths are closed" is a bounded claim; "the agent cannot cause harm" is not. Every new tool integration should identify both the authority it adds and how that authority will be withdrawn.&lt;/p&gt;

&lt;p&gt;For the invoice agent, ending model access is not the business objective; preventing further duplicate submissions and export changes is. If queued work needs no further model call, model denial does not end that exposure. Prioritize interventions by the effects that remain possible, not by which administrative action is easiest. Otherwise a convenient control operation quietly becomes the organization's definition of successful containment.&lt;/p&gt;

&lt;h2&gt;
  
  
  Token revocation changes authorization, not execution
&lt;/h2&gt;

&lt;p&gt;"Revoke the token" can mean stopping issuance, invalidating a refresh token or access token, or making a resource reject an existing token. &lt;a href="https://www.rfc-editor.org/rfc/rfc7009.html" rel="noopener noreferrer"&gt;RFC 7009&lt;/a&gt; requires refresh-token revocation support, recommends access-token revocation support and acknowledges propagation delay. These are not interchangeable guarantees.&lt;/p&gt;

&lt;p&gt;A successful &lt;a href="https://www.rfc-editor.org/rfc/rfc7009.html" rel="noopener noreferrer"&gt;revocation response&lt;/a&gt; does not prove resource-side rejection. Self-contained tokens can be validated locally; an issuer-side change therefore needs &lt;a href="https://www.rfc-editor.org/rfc/rfc7009.html" rel="noopener noreferrer"&gt;additional resource interaction&lt;/a&gt; for immediate enforcement. Waiting for expiry is a different shutdown assumption.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7662.html" rel="noopener noreferrer"&gt;Token introspection&lt;/a&gt; lets a protected resource ask whether a token is active:&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;"active"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&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;That response carries authorization information, not a termination instruction. &lt;a href="https://www.rfc-editor.org/rfc/rfc7662.html" rel="noopener noreferrer"&gt;Cached active responses&lt;/a&gt; can preserve a revoked token's usability. Your design must identify participating resources and refresh behavior, not merely name the &lt;a href="https://www.rfc-editor.org/rfc/rfc7662.html" rel="noopener noreferrer"&gt;introspection protocol&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Microsoft Entra continuous access evaluation (CAE) improves enforcement for &lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-continuous-access-evaluation-workload" rel="noopener noreferrer"&gt;eligible single-tenant service principals calling Microsoft Graph&lt;/a&gt;, with clients declaring &lt;code&gt;cp1&lt;/code&gt; and handling claims challenges. Managed identities and third-party or multitenant applications are excluded from that &lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-continuous-access-evaluation-workload" rel="noopener noreferrer"&gt;workload CAE scope&lt;/a&gt;. For Microsoft Entra Agent ID, verify eligibility rather than assuming ordinary service-principal behavior applies.&lt;/p&gt;

&lt;p&gt;Assume the invoice agent's participating Graph path is now denied. Model access, local writes and accepted jobs remain. Revocation could suffice where every forbidden effect depends on a current participating-resource decision and no relevant work is already admitted. This architecture does not meet those conditions.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Request boundaries must leave no accepted bypass
&lt;/h2&gt;

&lt;p&gt;A gateway lets an operator withdraw access without the caller's cooperation. &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-authenticate-authorize-ai-apis" rel="noopener noreferrer"&gt;Separating caller and backend authentication&lt;/a&gt; also permits targeting one caller without necessarily withdrawing everyone's backend access.&lt;/p&gt;

&lt;p&gt;That advantage requires mandatory routes. The topology below separates &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-authenticate-authorize-ai-apis" rel="noopener noreferrer"&gt;model and tool requests&lt;/a&gt; from &lt;a href="https://blogs.windows.com/windowsdeveloper/2026/10/07/microsoft-execution-containers-policy-driven-containment-for-ai-agents/" rel="noopener noreferrer"&gt;local execution&lt;/a&gt;, because closing one does not establish the state of the others:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A["Agent runtime"] --&amp;gt; G["Model gateway"]
    G --&amp;gt; M["Model"]
    A --&amp;gt; T["Tool boundary"]
    T --&amp;gt; S["Downstream systems"]
    C["Shutdown control"] -. Deny .-&amp;gt; G
    C -. Deny .-&amp;gt; T
    C -. Quarantine .-&amp;gt; R["Host and network"]
    R -. Contains .-&amp;gt; A&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Backend authorization and &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-authenticate-authorize-ai-apis" rel="noopener noreferrer"&gt;network restrictions&lt;/a&gt; must prevent bypass. Azure API Management's &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/return-response-policy" rel="noopener noreferrer"&gt;&lt;code&gt;return-response&lt;/code&gt; policy&lt;/a&gt; illustrates denial before backend forwarding in an inbound policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;return-response&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;set-status&lt;/span&gt; &lt;span class="na"&gt;code=&lt;/span&gt;&lt;span class="s"&gt;"403"&lt;/span&gt; &lt;span class="na"&gt;reason=&lt;/span&gt;&lt;span class="s"&gt;"Forbidden"&lt;/span&gt; &lt;span class="nt"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;set-header&lt;/span&gt; &lt;span class="na"&gt;name=&lt;/span&gt;&lt;span class="s"&gt;"Content-Type"&lt;/span&gt; &lt;span class="na"&gt;exists-action=&lt;/span&gt;&lt;span class="s"&gt;"override"&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;value&amp;gt;&lt;/span&gt;application/json&lt;span class="nt"&gt;&amp;lt;/value&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;/set-header&amp;gt;&lt;/span&gt;
  &lt;span class="nt"&gt;&amp;lt;set-body&amp;gt;&lt;/span&gt;{"error":"agent_execution_disabled"}&lt;span class="nt"&gt;&amp;lt;/set-body&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/return-response&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The fragment denies every caller reaching its &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/return-response-policy" rel="noopener noreferrer"&gt;policy scope&lt;/a&gt;. Targeting an agent instead requires &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/claims-validation" rel="noopener noreferrer"&gt;validated caller claims&lt;/a&gt;, not a supplied agent-name header. Distinguish delegated user identity from client identity; shared principals require another trusted discriminator or an explicitly broader shutdown.&lt;/p&gt;

&lt;p&gt;Shutdown granularity is therefore an architectural choice made before the incident. Sharing an identity may simplify operation while making isolation of one caller harder. And &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/return-response-policy" rel="noopener noreferrer"&gt;&lt;code&gt;return-response&lt;/code&gt;&lt;/a&gt; governs the current pipeline, not jobs already admitted elsewhere.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;br&gt;
Credential custody determines whether denial can be bypassed. If the agent retains a provider key and direct access, an &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-authenticate-authorize-ai-apis" rel="noopener noreferrer"&gt;alternative authentication path&lt;/a&gt; survives. Keep backend authority outside the runtime: API Management's &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-authenticate-authorize-ai-apis" rel="noopener noreferrer"&gt;own managed identity&lt;/a&gt; or Docker's supported &lt;a href="https://docs.docker.com/ai/sandboxes/architecture/" rel="noopener noreferrer"&gt;host-side credential injection&lt;/a&gt; can mediate use without exposing that credential to the workload.&lt;/p&gt;

&lt;p&gt;Credential custody also determines the incident's blast radius. If the backend credential remains under trusted control, denying the affected caller can preserve service for unrelated workloads. If that credential escaped, caller denial no longer covers its possible use elsewhere. The procedure must then address the credential itself, with any broader disruption explicitly accepted rather than hidden behind a claim of per-agent isolation.&lt;/p&gt;

&lt;p&gt;Credential modernization is not bypass elimination: Azure AI's &lt;a href="https://learn.microsoft.com/en-us/azure/templates/microsoft.cognitiveservices/accounts" rel="noopener noreferrer"&gt;&lt;code&gt;disableLocalAuth&lt;/code&gt;&lt;/a&gt; disables key-based authentication, while Anthropic's &lt;a href="https://platform.claude.com/docs/en/manage-claude/workload-identity-federation" rel="noopener noreferrer"&gt;workload identity federation&lt;/a&gt; replaces static keys for the configured flow. Neither alone makes a gateway mandatory.&lt;/p&gt;

&lt;p&gt;Managed identity likewise does not imply instant token revocation; it is outside &lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-continuous-access-evaluation-workload" rel="noopener noreferrer"&gt;workload CAE&lt;/a&gt;. For the example, assume accepted bypasses are now closed and model and tool routes deny the caller. Local execution, admitted jobs and replacement remain.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Local effects require host and network enforcement
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;Start with the forbidden effects, not the preferred sandbox.&lt;/strong&gt; Network isolation can deny remote submissions while leaving local writes possible. &lt;a href="https://blogs.windows.com/windowsdeveloper/2026/10/07/microsoft-execution-containers-policy-driven-containment-for-ai-agents/" rel="noopener noreferrer"&gt;Microsoft Execution Containers (MXC)&lt;/a&gt; can contain a tool or an entire agent; choose the unit at which an independent operator can control the relevant effect.&lt;/p&gt;

&lt;p&gt;Isolation does not remove deliberately granted access. Docker's local sandboxes use &lt;a href="https://github.com/docker/docs/blob/db384a4bd8b8f7eefbd6ed3796e49b5bfa517aad/content/manuals/ai/sandboxes/security/isolation.md" rel="noopener noreferrer"&gt;lightweight virtual machines, or microVMs, with separate Linux kernels&lt;/a&gt;, yet writes through &lt;a href="https://docs.docker.com/ai/sandboxes/architecture/" rel="noopener noreferrer"&gt;directly mounted workspaces&lt;/a&gt; affect host files immediately. The invoice agent's writable export directory is an authorized capability, not an isolation failure. Its withdrawal still needs a control.&lt;/p&gt;

&lt;p&gt;Likewise, Docker's &lt;a href="https://docs.docker.com/ai/sandboxes/architecture/" rel="noopener noreferrer"&gt;outbound policy proxy&lt;/a&gt; must cover the actual destinations and connection states. MXC's &lt;a href="https://blogs.windows.com/windowsdeveloper/2026/10/07/microsoft-execution-containers-policy-driven-containment-for-ai-agents/" rel="noopener noreferrer"&gt;permissive mode&lt;/a&gt; records rather than blocks ungranted access; enforcement and learning modes block it. Count only deployed, enforcing interventions, not &lt;a href="https://blogs.windows.com/windowsdeveloper/2026/10/07/microsoft-execution-containers-policy-driven-containment-for-ai-agents/" rel="noopener noreferrer"&gt;experimental or forthcoming capabilities&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/windows-365/agents/introduction-windows-365-for-agents" rel="noopener noreferrer"&gt;Windows 365 for Agents&lt;/a&gt; offers managed Cloud PCs with session orchestration and intervention capabilities. To use that isolation platform in the procedure, identify the assigned session, independent operator, available intervention and collateral impact.&lt;/p&gt;

&lt;p&gt;Suppose the export process has now stopped. Local writes and new registered requests have ended; jobs accepted by other systems have not necessarily ended with them.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Independent authority must keep the stop latched
&lt;/h2&gt;

&lt;p&gt;If the supervisor replaces the terminated process, routine recovery defeats containment. The operator stopped an instance, not the logical agent. A durable administrative stop must govern admission, retries and replacement from the start of the incident until independent clearance.&lt;/p&gt;

&lt;p&gt;The workload cannot authorize its own restart or remove its restrictions. An agent must not be its own &lt;a href="https://blogs.windows.com/windowsdeveloper/2026/10/07/microsoft-execution-containers-policy-driven-containment-for-ai-agents/" rel="noopener noreferrer"&gt;security authority&lt;/a&gt;, and the controller needs authority and information that survive the target losing access.&lt;/p&gt;

&lt;p&gt;Connect the logical agent to principals, runtime instances, boundaries and accepted jobs. &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/claims-validation" rel="noopener noreferrer"&gt;Validated identity&lt;/a&gt; alone does not supply that inventory. Record external job identifiers, owning services and available interventions outside the submitting process so termination does not destroy the means to cancel its work.&lt;/p&gt;

&lt;p&gt;Independent control does not require one all-powerful credential. Keep intervention privileges at the relevant boundaries, with named owners and a coordinated procedure. The requirement is that each intervention remains available after the target loses access. If stopping the agent also disables the only credential that can inspect or cancel its submitted jobs, the procedure has removed its own ability to finish.&lt;/p&gt;

&lt;p&gt;Include uncertain submissions. If a service accepted a job but its response was lost, the missing identifier leaves an unresolved obligation. Reconcile the attempt with the owning service; until then, retain an explicit coverage gap.&lt;/p&gt;

&lt;p&gt;If a downstream service offers no cancellation, independently verified completion may be the only route to quiescence. That waiting time belongs in the stop budget. If the organization cannot tolerate it, constrain what the agent can admit before delegating that capability. During the incident, an uncancellable job remains outstanding work; tracking its identifier does not turn it into a stopped job or remove the need to assess its effects.&lt;/p&gt;

&lt;p&gt;The protocol follows those dependencies: prevent replacement, close paths, stop execution and reconcile admitted work. Some interventions can run in parallel, but the control planes do not constitute one atomic transaction. Gateway denial can succeed while execution or an external job remains active.&lt;/p&gt;

&lt;p&gt;Record requested actions separately from observed outcomes. Name an escalation owner and any permitted broader intervention, including its collateral impact. A per-agent failure must not silently become a shared-service shutdown, nor a successful command become a claim that every path is closed.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;
&lt;h2&gt;
  
  
  Acceptance requires coverage, timing and persistence
&lt;/h2&gt;

&lt;p&gt;A command response is not an acceptance test: &lt;a href="https://www.rfc-editor.org/rfc/rfc7009.html" rel="noopener noreferrer"&gt;RFC 7009 returns HTTP 200 even for an already-invalid token&lt;/a&gt;. Use controlled fixtures to verify effects across every in-scope path:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Acceptance test&lt;/th&gt;
&lt;th&gt;Required observation&lt;/th&gt;
&lt;th&gt;What failure means&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Replay a pre-incident credential against the participating resource&lt;/td&gt;
&lt;td&gt;Forbidden action rejected within the agreed interval&lt;/td&gt;
&lt;td&gt;Resource enforcement cannot carry that path's guarantee&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Submit through each denied gateway route&lt;/td&gt;
&lt;td&gt;Requests denied before forwarding across serving instances&lt;/td&gt;
&lt;td&gt;Route remains open or convergence is unverified&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Attempt each registered direct-provider route&lt;/td&gt;
&lt;td&gt;No accepted independent route for the affected workload&lt;/td&gt;
&lt;td&gt;Credential custody or backend access permits a bypass&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Continue a harmless local-write fixture and observe the runtime&lt;/td&gt;
&lt;td&gt;Writes cease; execution also stops where quiescence is required&lt;/td&gt;
&lt;td&gt;Local effects or required execution remain outside stop coverage&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Reconcile accepted jobs against the trusted inventory&lt;/td&gt;
&lt;td&gt;Every in-scope job cancelled or independently proven terminal&lt;/td&gt;
&lt;td&gt;Admitted work remains active or its outcome is unknown&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Exercise queued retries and replacement instances&lt;/td&gt;
&lt;td&gt;No submission-capable replacement before clearance&lt;/td&gt;
&lt;td&gt;Instance termination mistaken for a durable agent stop&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The following YAML is an example operational contract, &lt;strong&gt;not product configuration&lt;/strong&gt;. Its 15-, 30- and 60-second thresholds are illustrative targets to negotiate and test, not vendor latency guarantees:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight yaml"&gt;&lt;code&gt;&lt;span class="na"&gt;agent&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;invoice-reviewer&lt;/span&gt;
&lt;span class="na"&gt;scope&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;one-agent&lt;/span&gt;
&lt;span class="na"&gt;clock_starts_at&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;incident_trigger_accepted&lt;/span&gt;
&lt;span class="na"&gt;required_observations&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;model_tool_and_credential_paths_denied&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;within_seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;15&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;independent_authenticated_probes_denied&lt;/span&gt;
  &lt;span class="na"&gt;runtime_execution_stopped&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;within_seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;30&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;supervisor_confirms_no_agent_execution&lt;/span&gt;
  &lt;span class="na"&gt;known_downstream_jobs&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
    &lt;span class="na"&gt;within_seconds&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="m"&gt;60&lt;/span&gt;
    &lt;span class="na"&gt;evidence&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;every_job_cancelled_or_proven_terminal&lt;/span&gt;
&lt;span class="na"&gt;restart&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;automatic&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;forbidden&lt;/span&gt;
  &lt;span class="na"&gt;requires&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;independent_approval&lt;/span&gt;
&lt;span class="na"&gt;failure&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt;
  &lt;span class="na"&gt;state&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;containment_incomplete&lt;/span&gt;
  &lt;span class="na"&gt;action&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;escalate_and_keep_stop_latched&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Choose the time budget from acceptable exposure, not a convenient vendor timeout. Fast denial on one route fails coverage; comprehensive but late denial fails timing. An unavailable enforcement point leaves an unverified path, not a latency to average away.&lt;/p&gt;

&lt;p&gt;Test the intended denial, not merely a failed request. A controlled action should be known to work before the stop, and its rejection afterward should be attributable to the selected enforcement point. A timeout, malformed credential or unrelated outage cannot establish that the procedure withdrew authority. Capture the trigger, target, observed result and enforcement evidence without copying raw credentials into the report.&lt;/p&gt;

&lt;p&gt;Test old credentials and &lt;a href="https://www.rfc-editor.org/rfc/rfc7662.html" rel="noopener noreferrer"&gt;cached authorization decisions&lt;/a&gt;, not only new tokens. Preserve running work, queued retries and replacement behavior in the fixtures. Verify the &lt;a href="https://blogs.windows.com/windowsdeveloper/2026/10/07/microsoft-execution-containers-policy-driven-containment-for-ai-agents/" rel="noopener noreferrer"&gt;execution boundary&lt;/a&gt; independently of model access, and reconcile jobs against service-observed outcomes.&lt;/p&gt;

&lt;p&gt;Suppose gateway denial is verified at eight seconds, runtime termination at 22 and the last job becomes terminal at 41. With all required paths covered, those illustrative observations support a 41-second stop, not an eight-second stop. A submission-capable replacement at 43 seconds invalidates it.&lt;/p&gt;

&lt;p&gt;Record coverage, completion times, unresolved paths and the observation window. Distinguish cancellation from completion: a terminal job may already have submitted the duplicate, just as &lt;a href="https://docs.docker.com/ai/sandboxes/architecture/" rel="noopener noreferrer"&gt;host-file writes&lt;/a&gt; may already have changed an export. Shutdown ends further activity; it does not certify earlier results.&lt;/p&gt;

&lt;p&gt;Choose an observation window that exercises the deployment's relevant retry and replacement behavior. A snapshot showing no process at one instant is weaker evidence than a denied restart attempt under the latched stop. The report should say which behaviors were tested and which remain unverified. That preserves the boundary between a demonstrated operational result and an unlimited assurance about future execution.&lt;/p&gt;

&lt;p&gt;&lt;a&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  Re-entry requires independent clearance
&lt;/h3&gt;

&lt;p&gt;Restarting does not establish that the cause changed. &lt;a href="https://docs.docker.com/ai/sandboxes/architecture/" rel="noopener noreferrer"&gt;Persistent sandbox state&lt;/a&gt; and queued work can reproduce the incident. An &lt;a href="https://blogs.windows.com/windowsdeveloper/2026/10/07/microsoft-execution-containers-policy-driven-containment-for-ai-agents/" rel="noopener noreferrer"&gt;independent approver&lt;/a&gt; must authorize reused state and restored permissions after the cause is addressed.&lt;/p&gt;

&lt;p&gt;Likewise, returning a Cloud PC to its pool is &lt;a href="https://learn.microsoft.com/en-us/windows-365/agents/introduction-windows-365-for-agents" rel="noopener noreferrer"&gt;resource lifecycle&lt;/a&gt;, not incident clearance. Choose controls, including &lt;a href="https://www.rfc-editor.org/rfc/rfc7009.html" rel="noopener noreferrer"&gt;resource-enforced revocation&lt;/a&gt;, for the authority they remove rather than the length of the security checklist.&lt;/p&gt;

&lt;p&gt;Delegating work creates an obligation to retain independent withdrawal and account for work already set in motion. For every action that survives the stop command, require an owner, an intervention and evidence of its result. &lt;strong&gt;A product name is not that answer.&lt;/strong&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7009.html" rel="noopener noreferrer"&gt;OAuth 2.0 token revocation, RFC 7009&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc7662.html" rel="noopener noreferrer"&gt;OAuth 2.0 token introspection, RFC 7662&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/concept-continuous-access-evaluation-workload" rel="noopener noreferrer"&gt;Continuous access evaluation for workload identities&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-authenticate-authorize-ai-apis" rel="noopener noreferrer"&gt;Authenticate and authorize access to AI APIs in Azure API Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/api-management/return-response-policy" rel="noopener noreferrer"&gt;Azure API Management return-response policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/claims-validation" rel="noopener noreferrer"&gt;Secure applications and APIs by validating claims&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/templates/microsoft.cognitiveservices/accounts" rel="noopener noreferrer"&gt;Microsoft.CognitiveServices accounts resource reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://blogs.windows.com/windowsdeveloper/2026/10/07/microsoft-execution-containers-policy-driven-containment-for-ai-agents/" rel="noopener noreferrer"&gt;Microsoft Execution Containers: Policy-driven containment for AI agents&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.docker.com/ai/sandboxes/architecture/" rel="noopener noreferrer"&gt;Docker Sandboxes architecture&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/docker/docs/blob/db384a4bd8b8f7eefbd6ed3796e49b5bfa517aad/content/manuals/ai/sandboxes/security/isolation.md" rel="noopener noreferrer"&gt;Docker Sandboxes isolation documentation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/windows-365/agents/introduction-windows-365-for-agents" rel="noopener noreferrer"&gt;What is Windows 365 for Agents?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://platform.claude.com/docs/en/manage-claude/workload-identity-federation" rel="noopener noreferrer"&gt;Anthropic workload identity federation&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aiagents</category>
      <category>apigateway</category>
      <category>enterprisearchitecture</category>
      <category>microsoftentra</category>
    </item>
    <item>
      <title>To passkey or not to passkey? Microsoft Entra has changed the question</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Wed, 07 Oct 2026 10:33:00 +0000</pubDate>
      <link>https://dev.to/astaykov/to-passkey-or-not-to-passkey-microsoft-entra-has-changed-the-question-3hlg</link>
      <guid>https://dev.to/astaykov/to-passkey-or-not-to-passkey-microsoft-entra-has-changed-the-question-3hlg</guid>
      <description>&lt;p&gt;Scepticism about passkey onboarding is understandable. It is not a reason to evaluate today's &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2" rel="noopener noreferrer"&gt;synced-passkey support in Microsoft Entra&lt;/a&gt; through yesterday's Authenticator Passwordless sign-in setup. For an employee signing in on their own phone, the important change is how much of the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;registration journey can stay on that phone&lt;/a&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  A customer needed one working phone, not a passwordless programme
&lt;/h2&gt;

&lt;p&gt;The discussion started with a concrete customer situation: front-line workers needed access to a web-based application from their personal mobile devices, outside the corporate network. The company did not provide laptops, mobile phones or other authentication hardware. There were no terminals on the manufacturing floor, and deploying terminals was not the desired answer.&lt;/p&gt;

&lt;p&gt;The company also did not want to distribute passwords. That matters because a recommendation built around sending every worker an initial password changes the requirement before the architecture discussion has even started. The desired outcome was a usable access journey on the device the worker already owned, not a new fleet-management project.&lt;/p&gt;

&lt;p&gt;The existing &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-passwordless-phone" rel="noopener noreferrer"&gt;authenticator passwordless sign-in&lt;/a&gt; onboarding was generating too many support requests. In the customer's discussion, approximately half the users were reportedly unable to complete the configuration. That is the reported experience of this population, not an industry-wide failure rate or a measured comparison with a completed passkey deployment.&lt;/p&gt;

&lt;p&gt;One contributing factor was that workers were being asked to configure an authenticator application both for multifactor authentication (MFA) and for passwordless sign-in. Those are &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-authenticator-app" rel="noopener noreferrer"&gt;different capabilities inside Microsoft Authenticator&lt;/a&gt;, not interchangeable names for the same setup. For the worker, the distinction appeared as more instructions, more settings and more opportunities to get stuck.&lt;/p&gt;

&lt;p&gt;It is tempting to answer this with the architecture that works well for a managed corporate laptop. Require a particular authenticator, prohibit synced credentials, standardize the hardware, then write a guide around that standard. That is a coherent answer to a different problem.&lt;/p&gt;

&lt;p&gt;Here, neither a managed computer nor a company-issued phone was available. Requiring one as the starting point would not simplify the customer scenario; it would replace it. The useful question was whether the worker's existing phone could provide both the application surface and the authentication experience.&lt;/p&gt;

&lt;p&gt;This is why an account of difficult cross-device registration in a restrictive corporate environment is relevant evidence, but not a verdict. You still need to test the experience under the actual &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-fido2-compatibility" rel="noopener noreferrer"&gt;device, browser and provider combinations that Entra supports&lt;/a&gt;. A phone-only browser journey deserves its own evaluation.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Optimize the journey the worker must complete, not the deployment you would prefer to manage.&lt;/strong&gt; The device constraint is part of the design brief, not a detail to remove from it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The recent Entra changes make this a different discussion
&lt;/h2&gt;

&lt;p&gt;There are specific changes behind this recommendation, not just renewed enthusiasm for passkeys. Microsoft lists &lt;a href="https://learn.microsoft.com/en-us/entra/fundamentals/whats-new#march-2026" rel="noopener noreferrer"&gt;synced passkeys as generally available in March 2026&lt;/a&gt;, with passkey profiles controlling their use alongside device-bound credentials. An evaluation based on an earlier device-bound-only assumption is therefore evaluating a different set of available choices.&lt;/p&gt;

&lt;p&gt;Microsoft then lists &lt;a href="https://learn.microsoft.com/en-us/entra/fundamentals/whats-new#may-2026" rel="noopener noreferrer"&gt;passkey support in registration campaigns as generally available in May 2026&lt;/a&gt;. The initial experience is optimized for users whose passkey profile has no restrictions. That is particularly relevant when the goal is to let a heterogeneous population use an available provider rather than an administrator's predicted provider.&lt;/p&gt;

&lt;p&gt;There is also a more recent default change. From September 1, 2026, Microsoft documents &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-sms-voice-retirement" rel="noopener noreferrer"&gt;automatic passkey enablement and registration nudges for users enabled for SMS or voice authentication&lt;/a&gt;, with a documented temporary opt-out. This is not a statement that every user is already enrolled, that every tenant now has the same policy, or that passwords have disappeared.&lt;/p&gt;

&lt;p&gt;Those adoption nudges are useful context, but they do not bootstrap a worker who has no usable sign-in method yet. A campaign that &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-sms-voice-retirement" rel="noopener noreferrer"&gt;appears after an existing authentication succeeds&lt;/a&gt; and a first-credential onboarding process solve different problems. The latter still needs a deliberate entry path, described later.&lt;/p&gt;

&lt;p&gt;The practical change is that Entra supports passkeys in &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2" rel="noopener noreferrer"&gt;native and third-party providers as well as Microsoft Authenticator and security keys&lt;/a&gt;. For a supported phone with its provider already configured, the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;synced-passkey registration flow&lt;/a&gt; can use that provider. A separate Authenticator installation is not inherently part of that journey.&lt;/p&gt;

&lt;p&gt;Notice the qualification: &lt;em&gt;with its provider already configured&lt;/em&gt;. Microsoft explicitly documents a &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;password-manager prerequisite for synced-passkey registration&lt;/a&gt;, including platform-specific settings. The improvement is not that every phone arrives in an identical state; it is that a usable existing state no longer has to be replaced with an additional application setup.&lt;/p&gt;

&lt;p&gt;It would also be wrong to freeze Authenticator at an old onboarding experience. Microsoft now documents &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey-authenticator" rel="noopener noreferrer"&gt;direct passkey registration from the Authenticator application&lt;/a&gt;, including configuration of passkey, passwordless and MFA capabilities according to account policies. That is a legitimate alternative when Authenticator is the chosen provider.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Reassess the current supported experience, not the reputation of an older one.&lt;/strong&gt; For this customer, the meaningful improvement is a shorter same-phone route, not a promise that every operating-system dialog has become identical.&lt;/p&gt;

&lt;h2&gt;
  
  
  Authenticator is an application, not an authentication method
&lt;/h2&gt;

&lt;p&gt;"Use Authenticator" is not a sufficiently precise authentication recommendation. The application supports &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-authenticator-app" rel="noopener noreferrer"&gt;MFA notifications and verification codes, passwordless phone sign-in, and device-bound passkeys&lt;/a&gt;. Those capabilities do not have the same enrollment requirements or the same authentication strength.&lt;/p&gt;

&lt;p&gt;The distinction that matters most is &lt;em&gt;passwordless&lt;/em&gt; versus &lt;em&gt;phishing-resistant&lt;/em&gt;. Microsoft's &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-strengths" rel="noopener noreferrer"&gt;built-in authentication-strength definitions&lt;/a&gt; place Authenticator phone sign-in in passwordless MFA, but not phishing-resistant MFA. Removing the password prompt is valuable; it is not the same property as requiring a phishing-resistant credential.&lt;/p&gt;

&lt;p&gt;Passkeys use Fast Identity Online 2 (FIDO2) authentication. Entra treats &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2" rel="noopener noreferrer"&gt;both synced and device-bound passkeys as phishing-resistant&lt;/a&gt;, while giving administrators controls over which credentials they permit. The storage decision adds a separate trust question; it does not turn a synced passkey into an Authenticator notification.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Experience&lt;/th&gt;
&lt;th&gt;Passwordless?&lt;/th&gt;
&lt;th&gt;Phishing-resistant?&lt;/th&gt;
&lt;th&gt;What the worker is configuring&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Authenticator MFA, using a notification or verification code&lt;/td&gt;
&lt;td&gt;Not by itself; it supplies an additional factor&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;An &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-authenticator-app" rel="noopener noreferrer"&gt;MFA method in the application&lt;/a&gt;, commonly used alongside a password&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Authenticator passwordless phone sign-in&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;The application's &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-strengths" rel="noopener noreferrer"&gt;passwordless phone-sign-in capability&lt;/a&gt;, distinct from a passkey&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Device-bound passkey in Authenticator&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;A &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-authenticator-app" rel="noopener noreferrer"&gt;FIDO2 credential bound to that phone&lt;/a&gt;, with Authenticator acting as its provider&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Synced passkey in a native or third-party provider&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;A &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2" rel="noopener noreferrer"&gt;FIDO2 credential managed and synced by the selected provider&lt;/a&gt;, rather than necessarily by Authenticator&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The first row often leaves the worker with &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-authenticator-app" rel="noopener noreferrer"&gt;a password plus an additional verification step&lt;/a&gt;. The second removes the password from the sign-in experience, but remains a different method from &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-strengths" rel="noopener noreferrer"&gt;FIDO2 passkey authentication&lt;/a&gt;. You cannot infer the required strength from the fact that the same application appears on the screen.&lt;/p&gt;

&lt;p&gt;The third row is not "phone sign-in with a new label." Microsoft documents &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-authenticator-app#device-bound-passkey" rel="noopener noreferrer"&gt;Authenticator passkeys as device-bound credentials&lt;/a&gt;. Choosing that option is a credential decision, not just enabling the application's older passwordless-notification feature.&lt;/p&gt;

&lt;p&gt;The fourth row moves the credential-storage relationship into a passkey provider such as Apple Passwords or Google Password Manager. Microsoft documents &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#what-are-synced-versus-device-bound-passkeys" rel="noopener noreferrer"&gt;provider-dependent synchronization across the user's devices&lt;/a&gt;. It is not a promise of unrestricted portability between every ecosystem.&lt;/p&gt;

&lt;p&gt;The worker does not need this taxonomy on the welcome sheet. You need it in the design review so that "passwordless," "Authenticator" and "passkey" do not silently become synonyms. Otherwise, an apparently small wording error becomes a different enrollment path or an authentication-strength mismatch.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;An application name is not a security property.&lt;/strong&gt; Specify the credential and the required strength before deciding which application, if any, the worker must configure.&lt;/p&gt;

&lt;h2&gt;
  
  
  Same-device is the design choice that removes the detour
&lt;/h2&gt;

&lt;p&gt;There are two independent axes in this discussion. &lt;em&gt;Synced versus device-bound&lt;/em&gt; describes how the credential is stored and made available; &lt;em&gt;same-device versus cross-device&lt;/em&gt; describes where the application and the authenticator are used. Microsoft documents &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-sign-in-passkey-authenticator" rel="noopener noreferrer"&gt;both same-device and cross-device sign-in with device-bound Authenticator passkeys&lt;/a&gt;, so device-bound does not mean cross-device.&lt;/p&gt;

&lt;p&gt;For the customer scenario, same-device means the worker opens the application on the phone and uses an available passkey provider on that phone. The supported &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;synced-passkey setup&lt;/a&gt; follows that local provider experience. The user does not need a second screen merely to make the intended phone-only journey work.&lt;/p&gt;

&lt;p&gt;Phone-assisted cross-device sign-in is different: the application is on a computer, while the passkey is on the phone. Microsoft's &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-sign-in-passkey" rel="noopener noreferrer"&gt;documented synced-passkey flow&lt;/a&gt; uses a QR code and requires Bluetooth and internet connectivity on both devices. Those are additional participants and dependencies, not properties that every passkey sign-in necessarily has.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Same-device sign-in&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    A["Web app on phone"] --&amp;gt; B["Passkey provider&amp;lt;br/&amp;gt;on that phone"]
    B --&amp;gt; C["Local verification:&amp;lt;br/&amp;gt;face, fingerprint or PIN"]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;&lt;strong&gt;Phone-assisted cross-device sign-in&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    D["Web app on computer"] --&amp;gt; E["QR code, Bluetooth&amp;lt;br/&amp;gt;and internet"]
    E --&amp;gt; F["Passkey provider&amp;lt;br/&amp;gt;on phone"]
    F --&amp;gt; G["Local verification:&amp;lt;br/&amp;gt;face, fingerprint or PIN"]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Both paths still need to complete authentication and meet the application's &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-strengths" rel="noopener noreferrer"&gt;Conditional Access authentication requirements&lt;/a&gt;. The two diagrams separate the user-facing topology, not different levels of permission. Cross-device is a useful capability when there actually is another device to sign in on.&lt;/p&gt;

&lt;p&gt;A synced passkey does not force a cross-device ceremony either. If the provider has made the credential available locally on another supported device, that device can &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-sign-in-passkey" rel="noopener noreferrer"&gt;use the local credential rather than borrowing the phone&lt;/a&gt;. That follows the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#what-are-synced-versus-device-bound-passkeys" rel="noopener noreferrer"&gt;provider-dependent availability model for synced passkeys&lt;/a&gt;; it is a different situation from scanning a computer's code with a phone.&lt;/p&gt;

&lt;p&gt;Why start a phone-only worker's registration guide on a computer?&lt;/p&gt;

&lt;p&gt;For this scenario, that adds a device the worker was never promised. It also moves the demonstration away from the experience the company actually wants to support. A guide that begins on the personal phone keeps the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;registration flow and its provider selection&lt;/a&gt; in the same place as subsequent application use.&lt;/p&gt;

&lt;p&gt;This is where the experience becomes almost unremarkable on a supported, correctly configured phone: select the passkey flow, confirm with the device's local verification, finish registration, then use the credential for sign-in. That is the target the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;documented registration&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-sign-in-passkey" rel="noopener noreferrer"&gt;local sign-in experiences&lt;/a&gt; make possible. It is not a claim that every browser, old handset or provider configuration will behave identically.&lt;/p&gt;

&lt;p&gt;  &lt;iframe src="https://www.youtube.com/embed/70Ec7FlIYiQ" width="710" height="399"&gt;
  &lt;/iframe&gt;
&lt;/p&gt;

&lt;p&gt;The demonstration worth showing is the complete same-phone journey.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the intended journey local before evaluating cross-device exceptions.&lt;/strong&gt; Credential storage and authentication topology are separate decisions, and neither should be inferred from the other.&lt;/p&gt;

&lt;h2&gt;
  
  
  A permissive worker profile is a deliberate trust decision
&lt;/h2&gt;

&lt;p&gt;For this particular population, the proposed starting point is a passkey profile without provider restrictions. Allow both device-bound and synced passkeys, do not enforce attestation, and do not target specific authenticator identifiers. These are &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2" rel="noopener noreferrer"&gt;supported passkey-profile choices&lt;/a&gt;, scoped to the relevant worker group rather than automatically applied to the whole tenant.&lt;/p&gt;

&lt;p&gt;An &lt;em&gt;Authenticator Attestation GUID (AAGUID)&lt;/em&gt; identifies an authenticator type, such as its make and model. &lt;em&gt;Attestation&lt;/em&gt; gives Entra evidence about the authenticator creating the credential. Microsoft documents &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#passkey-fido2-authenticator-attestation-guid-aaguid" rel="noopener noreferrer"&gt;both controls and their limitations&lt;/a&gt;; they are not equivalent to confirming that a worker's personal phone is company-managed.&lt;/p&gt;

&lt;p&gt;Here is the administrative intent, not an API payload. The self-service setting is &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#enable-passkey-profiles" rel="noopener noreferrer"&gt;global rather than profile-specific&lt;/a&gt;, which is a relevant distinction when reviewing existing configuration.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Population: Front-line worker pilot
Allow self-service set up (global): Yes
Enforce attestation (profile): No
Passkey types (profile): Device-bound + Synced
Target specific authenticators / AAGUID restrictions: Off
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The sharp edge is that &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#what-are-synced-versus-device-bound-passkeys" rel="noopener noreferrer"&gt;synced passkeys do not support attestation&lt;/a&gt;. Disabling attestation does not make FIDO2 authentication cease to be phishing-resistant, but it gives up verified authenticator provenance. Microsoft's warning is explicit: without enforced attestation, Entra cannot guarantee passkey attributes, including whether the credential is synced or device-bound.&lt;/p&gt;

&lt;p&gt;That is why an AAGUID allowlist without attestation is not a substitute for hardware assurance. Microsoft says to treat &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#create-a-new-passkey-profile" rel="noopener noreferrer"&gt;unattested AAGUID restrictions as policy guidance rather than a strict security control&lt;/a&gt;. If the requirement is an attested, approved, device-bound authenticator, use a design that actually supplies that assurance.&lt;/p&gt;

&lt;p&gt;For these workers, however, predicting one approved provider for every personal phone can add the constraint that the customer is trying to remove. An unrestricted profile permits the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;available native or third-party provider experience&lt;/a&gt; instead of blocking it through an administrator-maintained list. It does not guarantee that Entra will always choose the fastest provider or eliminate every provider-selection prompt.&lt;/p&gt;

&lt;p&gt;The expectation in the discussion was that most users would register a synced passkey and a smaller number might choose a device-bound one. Treat that as a pilot hypothesis, not documented product behavior. Measure the registered methods and actual completion rate before turning an expectation into a rollout claim.&lt;/p&gt;

&lt;p&gt;There are also configuration boundaries to review before changing an existing tenant. Microsoft documents that &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#enable-passkey-profiles" rel="noopener noreferrer"&gt;opting into passkey profiles cannot be reversed and transfers existing settings to a Default profile&lt;/a&gt;. It also documents that users scoped for multiple profiles can register or authenticate when the credential fully satisfies at least one applicable profile.&lt;/p&gt;

&lt;p&gt;Do not accidentally put privileged accounts into a permissive worker scope and assume a stricter profile takes precedence. Conversely, do not assume tightening provider restrictions later is only a registration change: &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#create-a-new-passkey-profile" rel="noopener noreferrer"&gt;removing an allowed AAGUID can also prevent existing credentials from signing in&lt;/a&gt;. Scope and recovery are part of the policy change.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Less restrictive onboarding is a conscious trust decision, not absent security design.&lt;/strong&gt; Keep the worker profile separate from populations whose credential-provenance requirements are different.&lt;/p&gt;

&lt;h2&gt;
  
  
  Bootstrap registration before you enforce the credential
&lt;/h2&gt;

&lt;p&gt;The worker still needs a secure starting point. A &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass" rel="noopener noreferrer"&gt;Temporary Access Pass (TAP)&lt;/a&gt; is a time-limited passcode that can be configured for single use or multiple sign-ins and used to register passwordless authentication methods. It is the bootstrap credential, not the permanent method you want the worker to keep using.&lt;/p&gt;

&lt;p&gt;First verify the worker's identity through the established onboarding process. Then issue a short-lived TAP through a controlled delivery process, and ensure the user is actually &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass#enable-the-temporary-access-pass-policy" rel="noopener noreferrer"&gt;included in the TAP authentication-method policy&lt;/a&gt;. Creating a TAP for an account does not, by itself, make that account eligible to sign in with it.&lt;/p&gt;

&lt;p&gt;SMS, personal email and a physical help desk are possible delivery channels to assess, not interchangeable identity-proofing guarantees. The security question is who can obtain the bootstrap credential and register the next method. A printed code handed to a verified worker and a code sent to an unverified contact address do not establish the same trust contract.&lt;/p&gt;

&lt;p&gt;Prefer a single-use TAP where the tested journey supports it. Microsoft documents a &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass#limitations" rel="noopener noreferrer"&gt;10-minute completion limit for passwordless-method registration after sign-in with a one-time TAP&lt;/a&gt;, so do not write an onboarding process that spends that window on unrelated setup. The code is also &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass" rel="noopener noreferrer"&gt;only visible at creation&lt;/a&gt;, making secure handling and delivery part of the operational design.&lt;/p&gt;

&lt;p&gt;Conditional Access is where a sensible credential choice can still become a circular dependency. TAP satisfies the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-strengths" rel="noopener noreferrer"&gt;MFA authentication strength, not the phishing-resistant strength&lt;/a&gt;. Requiring an already-registered phishing-resistant method as the only way to reach its initial registration is not a stronger bootstrap; it is a route the new worker cannot complete.&lt;/p&gt;

&lt;p&gt;Separate the bootstrap-compatible &lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-all-users-security-info-registration" rel="noopener noreferrer"&gt;security-information registration policy&lt;/a&gt; from application access enforcement. Require an appropriate registration strength that permits the issued TAP, then require the intended credential for the application once the worker is ready. Review the complete applicable policy set, including broad resource scopes, rather than assuming one new registration policy neutralizes every other requirement.&lt;/p&gt;

&lt;p&gt;Be precise about the application's requirement too. The &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-strengths" rel="noopener noreferrer"&gt;built-in phishing-resistant MFA strength&lt;/a&gt; allows methods besides passkeys. If the requirement is specifically passkey-only access, use a &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#enforce-passkey-fido2-sign-in" rel="noopener noreferrer"&gt;custom authentication strength permitting Passkeys (FIDO2)&lt;/a&gt;, with restrictions consistent with the worker profile.&lt;/p&gt;

&lt;p&gt;The intended sequence is short. The &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass" rel="noopener noreferrer"&gt;TAP bootstrap&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;same-device passkey registration&lt;/a&gt; establish the method before application enforcement becomes the worker's gate.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant Support as Onboarding support
    participant Worker
    participant Entra
    participant Phone as Phone passkey provider
    Support-&amp;gt;&amp;gt;Worker: Verify identity and deliver time-limited TAP
    Worker-&amp;gt;&amp;gt;Entra: Open Security info on phone and sign in with TAP
    Entra--&amp;gt;&amp;gt;Phone: Request passkey creation
    Phone-&amp;gt;&amp;gt;Worker: Present provider and request local verification
    Phone--&amp;gt;&amp;gt;Entra: Complete credential registration
    Worker-&amp;gt;&amp;gt;Entra: Fresh web app sign-in using the passkey
    Entra--&amp;gt;&amp;gt;Worker: Application access after policy checks&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The worker-facing instruction should follow that sequence, using the labels in the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;documented registration flow&lt;/a&gt;. It should not ask the worker to choose a security architecture before they can open the application.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;On your personal phone:
1. Open the Security info link in your onboarding message.
2. Enter the work username supplied in that message.
3. Sign in using your Temporary Access Pass.
4. Choose Add sign-in method, then Passkey.
5. Continue with the passkey provider offered on this phone.
6. Confirm with your face, fingerprint or device PIN.
7. Name the passkey, finish, then test the web application sign-in.
8. If Passkey is missing or setup cannot finish, contact support.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Test this with a genuinely new account, not just an administrator's already-configured phone. Microsoft documents that some &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass#limitations" rel="noopener noreferrer"&gt;combined MFA or password-reset registration interruptions after TAP sign-in do not support FIDO2 registration&lt;/a&gt;. An unrelated registration requirement can therefore derail the carefully written passkey guide.&lt;/p&gt;

&lt;p&gt;Register, perform a fresh sign-in using the passkey, verify the application accepts the intended strength, and then enforce for the ready population. Use the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-all-users-security-info-registration#create-a-policy-to-secure-registration" rel="noopener noreferrer"&gt;documented report-only approach when evaluating registration policy&lt;/a&gt;, and keep an established recovery path. A recorded credential is not the same evidence as a successful application journey.&lt;/p&gt;

&lt;p&gt;One final distinction: &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass#use-a-temporary-access-pass" rel="noopener noreferrer"&gt;TAP does not replace or delete the account's password&lt;/a&gt;. This design avoids distributing a password and using one for this access journey. It does not, by itself, prove that every password-based route in the tenant has been removed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bootstrap establishes the credential; enforcement requires it.&lt;/strong&gt; Keep those stages connected without making either one depend on a method the worker does not yet possess.&lt;/p&gt;

&lt;h2&gt;
  
  
  Automate the surrounding work, not the user's private key
&lt;/h2&gt;

&lt;p&gt;Can onboarding be automated? Much of the surrounding work can, but installing an application and registering a personal credential are different operations. The supported &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;synced-passkey registration flow&lt;/a&gt; still includes the user interacting with the provider on their device.&lt;/p&gt;

&lt;p&gt;Microsoft Entra Lifecycle Workflows has a &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/lifecycle-workflow-tasks" rel="noopener noreferrer"&gt;built-in task that generates a TAP and emails it to the new user's manager&lt;/a&gt;. That is a concrete automation option, not a claim that a workflow silently creates a synced passkey in the worker's personal provider. The documented task also has prerequisites, including manager and manager-email attributes.&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/tutorial-onboard-custom-workflow-portal" rel="noopener noreferrer"&gt;prehire onboarding tutorial&lt;/a&gt; documents the relevant licensing requirements. If that fits the organization's operating model, automate eligibility, timing, manager notification and the supported bootstrap task. Still decide how the manager verifies the recipient and delivers the code without turning a sensitive credential into an indefinitely retained onboarding attachment.&lt;/p&gt;

&lt;p&gt;A third-party onboarding product can improve orchestration, guidance or verified delivery. Ask which step it automates and which device-side interaction remains. A claim of "automated passwordless onboarding" is not enough to establish that it removes the user steps in the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;supported registration flow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;There is a different administrator-led route for hardware. Microsoft documents &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#provision-fido2-security-keys-using-microsoft-graph-api-preview" rel="noopener noreferrer"&gt;FIDO2 security-key provisioning through Microsoft Graph and custom clients as a preview capability&lt;/a&gt;, including provisioning and registering a physical key for the user. That introduces hardware procurement, handling, distribution and replacement, so it changes this customer's no-additional-hardware scenario rather than automatically solving it.&lt;/p&gt;

&lt;p&gt;Recovery deserves the same attention as first registration. With a synced credential, the &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#what-are-synced-versus-device-bound-passkeys" rel="noopener noreferrer"&gt;provider's synchronization and account-access model&lt;/a&gt; becomes part of the trust decision. Do not assume that a worker replacing a phone always has the right provider account, settings and recovery access immediately available.&lt;/p&gt;

&lt;p&gt;With an Authenticator passkey, do not promise that restoring application backup moves the credential to new hardware. Microsoft explicitly states that &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/passkey-faq#can-i-restore-or-sync-authenticator-passkeys-to-a-new-device" rel="noopener noreferrer"&gt;Authenticator passkeys are device-bound and cannot be synced&lt;/a&gt;. Maintain a verified recovery process for workers who lose access to their existing method.&lt;/p&gt;

&lt;p&gt;Nor does phishing-resistant authentication establish the security posture of an unmanaged personal phone. The &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#create-a-new-passkey-profile" rel="noopener noreferrer"&gt;passkey policy's credential controls and attestation limits&lt;/a&gt; are not a device-management programme. Accepting this access model remains a business and security decision about the application and its exposure.&lt;/p&gt;

&lt;p&gt;The pilot should therefore measure more than whether a passkey can be created. Record unassisted registration completion, time to a fresh successful application sign-in, support contacts, provider distribution and recovery completion. Compare those outcomes with the existing journey before claiming the customer problem has been solved.&lt;/p&gt;

&lt;p&gt;The honest answer to "to passkey or not to passkey" is not a universal instruction to relax every policy. For this population, a broadly compatible passkey profile and same-device synced-passkey journey are the design worth testing first. A corporate population requiring attested, device-bound credentials has a different requirement and can reach a different answer.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Keep the administrative concepts in the control plane and the worker's task on the worker's phone.&lt;/strong&gt; The useful improvement is not asking workers to understand four authentication methods; it is no longer requiring them to navigate four methods to reach one application.&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/fundamentals/whats-new#march-2026" rel="noopener noreferrer"&gt;Microsoft Entra releases: March 2026 synced-passkey general availability&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/fundamentals/whats-new#may-2026" rel="noopener noreferrer"&gt;Microsoft Entra releases: May 2026 passkey registration campaigns&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-sms-voice-retirement" rel="noopener noreferrer"&gt;Passkeys by default and retirement of Microsoft-provided SMS and voice authentication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2" rel="noopener noreferrer"&gt;Enable passkeys and passkey profiles in Microsoft Entra ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#enable-passkey-profiles" rel="noopener noreferrer"&gt;Passkey-profile opt-in and global self-service registration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#what-are-synced-versus-device-bound-passkeys" rel="noopener noreferrer"&gt;Synced versus device-bound passkeys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#passkey-fido2-authenticator-attestation-guid-aaguid" rel="noopener noreferrer"&gt;Authenticator identifiers and attestation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#create-a-new-passkey-profile" rel="noopener noreferrer"&gt;Passkey-profile restrictions and attestation warnings&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#enforce-passkey-fido2-sign-in" rel="noopener noreferrer"&gt;Passkey-only Conditional Access authentication strength&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-authentication-passkeys-fido2#provision-fido2-security-keys-using-microsoft-graph-api-preview" rel="noopener noreferrer"&gt;Administrator provisioning of FIDO2 security keys, preview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-authenticator-app" rel="noopener noreferrer"&gt;Microsoft Authenticator authentication methods&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-passwordless-phone" rel="noopener noreferrer"&gt;Microsoft Authenticator passwordless phone sign-in&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-authenticator-app#device-bound-passkey" rel="noopener noreferrer"&gt;Device-bound passkeys in Microsoft Authenticator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-strengths" rel="noopener noreferrer"&gt;Conditional Access authentication strengths&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey-authenticator" rel="noopener noreferrer"&gt;Register passkeys in Microsoft Authenticator&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-register-passkey" rel="noopener noreferrer"&gt;Register a synced passkey&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-sign-in-passkey" rel="noopener noreferrer"&gt;Same-device and cross-device sign-in with synced passkeys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/how-to-sign-in-passkey-authenticator" rel="noopener noreferrer"&gt;Same-device and cross-device sign-in with Authenticator passkeys&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-fido2-compatibility" rel="noopener noreferrer"&gt;Passkey browser, operating-system and application compatibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass" rel="noopener noreferrer"&gt;Configure and use Temporary Access Pass&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass#enable-the-temporary-access-pass-policy" rel="noopener noreferrer"&gt;Temporary Access Pass policy targeting&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass#limitations" rel="noopener noreferrer"&gt;Temporary Access Pass registration limits&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/howto-authentication-temporary-access-pass#use-a-temporary-access-pass" rel="noopener noreferrer"&gt;Using Temporary Access Pass without replacing a password&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-all-users-security-info-registration" rel="noopener noreferrer"&gt;Protect security-information registration with Conditional Access&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-all-users-security-info-registration#create-a-policy-to-secure-registration" rel="noopener noreferrer"&gt;Evaluate a security-information registration policy before enforcement&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/lifecycle-workflow-tasks" rel="noopener noreferrer"&gt;Lifecycle Workflows built-in tasks&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/tutorial-onboard-custom-workflow-portal" rel="noopener noreferrer"&gt;Automate prehire onboarding with Lifecycle Workflows&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/passkey-faq#can-i-restore-or-sync-authenticator-passkeys-to-a-new-device" rel="noopener noreferrer"&gt;Authenticator passkey synchronization and recovery limitations&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>microsoftentra</category>
      <category>passkeys</category>
      <category>passwordless</category>
      <category>identitysecurity</category>
    </item>
    <item>
      <title>Agent-to-agent authorization with Entra Agent ID on AWS AgentCore</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 29 Sep 2026 09:07:00 +0000</pubDate>
      <link>https://dev.to/astaykov/agent-to-agent-authorization-with-entra-agent-id-on-aws-agentcore-2db5</link>
      <guid>https://dev.to/astaykov/agent-to-agent-authorization-with-entra-agent-id-on-aws-agentcore-2db5</guid>
      <description>&lt;p&gt;The first two versions of my Amazon Web Services (AWS) proof of value established cross-cloud Microsoft Entra Agent ID and removed the long-lived client secret. Version 3 answers the question customers now ask most often: what happens when one agent calls another?&lt;/p&gt;

&lt;p&gt;It is tempting to treat Agent2Agent (A2A) as a new identity problem because the caller and resource are both agents. It is not. &lt;strong&gt;A2A changes the communication contract. It does not replace the authentication and authorization contract.&lt;/strong&gt;&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;. &lt;a href="https://github.com/astaykov/agentid-agentcore/releases/tag/v3.0" rel="noopener noreferrer"&gt;Release v3.0&lt;/a&gt; adds that missing hop. Agent 1 delegates directory work to Agent 2 on behalf of a signed-in user. Agent 3 calls Agent 2 autonomously. Both use A2A, both present Microsoft Entra access tokens, and they receive deliberately different capabilities.&lt;/p&gt;

&lt;h2&gt;
  
  
  Version 3 makes agent-to-agent authorization the point
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://dev.to/astaykov/one-identity-two-clouds-an-aws-hosted-agent-that-authorizes-with-microsoft-entra-agent-id-2bkl"&gt;The first article&lt;/a&gt; proved that an AWS-hosted agent can use Entra Agent ID to reach Microsoft and AWS resources. Its purpose was cross-cloud identity portability.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://dev.to/astaykov/how-an-aws-hosted-entra-agent-id-drops-the-client-secret-1d4k"&gt;The second article&lt;/a&gt; replaced the blueprint client secret with an AWS Security Token Service (STS) assertion and a federated identity credential (FIC). Its purpose was secretless workload authentication.&lt;/p&gt;

&lt;p&gt;Version 3 begins where those two stop. It keeps the cross-cloud federation and introduces two more agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Agent 1 remains the user-facing orchestrator in AgentCore Runtime.&lt;/li&gt;
&lt;li&gt;Agent 2 is an A2A specialist in a second AgentCore Runtime.&lt;/li&gt;
&lt;li&gt;Agent 3 is an autonomous caller running in AWS Lambda and triggered by Amazon EventBridge Scheduler.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The &lt;a href="https://github.com/astaykov/agentid-agentcore/blob/v3.0/docs/a2a-setup-guide.md" rel="noopener noreferrer"&gt;v3 repository documentation&lt;/a&gt; makes the new authorization split explicit. Agent 1 calls Agent 2 with delegated user authority, while Agent 3 calls the same agent with application authority. Agent 2 exposes directory tools only to the delegated path.&lt;/p&gt;

&lt;p&gt;That is what v3 adds. Blueprint 2 becomes a resource boundary where two kinds of principals arrive through one protocol and must not collapse into one permission set.&lt;/p&gt;

&lt;h2&gt;
  
  
  A2A standardizes the message, not the trust decision
&lt;/h2&gt;

&lt;p&gt;The &lt;a href="https://a2a-protocol.org/latest/specification/" rel="noopener noreferrer"&gt;A2A specification&lt;/a&gt; defines a common model for agent discovery, messages, tasks, artifacts, and protocol bindings. An Agent Card advertises what an agent can do and how a client can reach it. A &lt;code&gt;message/send&lt;/code&gt; operation carries work from one agent to another.&lt;/p&gt;

&lt;p&gt;None of that turns an Agent Card into a permission grant.&lt;/p&gt;

&lt;p&gt;AWS makes the separation visible in its &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-a2a.html" rel="noopener noreferrer"&gt;AgentCore A2A deployment model&lt;/a&gt; and &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-a2a-protocol-contract.html" rel="noopener noreferrer"&gt;protocol contract&lt;/a&gt;. An A2A server exposes its Agent Card at &lt;code&gt;/.well-known/agent-card.json&lt;/code&gt;, receives JSON-RPC requests at the root path, and can authenticate clients with OAuth 2.0 bearer tokens or AWS Signature Version 4. AgentCore provides the transport and an authentication gate. The agent application still owns the claim-level authorization decision.&lt;/p&gt;

&lt;p&gt;The sample currently pins A2A protocol version &lt;code&gt;0.3.0&lt;/code&gt;, matching the implementation documented in the &lt;a href="https://github.com/astaykov/agentid-agentcore/blob/v3.0/docs/a2a-setup-guide.md" rel="noopener noreferrer"&gt;v3 setup guide&lt;/a&gt;. The current A2A specification lists &lt;code&gt;1.0.0&lt;/code&gt; as the latest released version. That version difference matters for interoperability work, but it does not change the identity principle: the protocol relies on standard web security rather than inventing an agent-specific token.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    User["User + browser"] --&amp;gt;|"Token for Blueprint 1"| A1["Agent 1&amp;lt;br/&amp;gt;orchestrator"]
    A1 --&amp;gt;|"A2A delegated token"| A2["Agent 2&amp;lt;br/&amp;gt;specialist"]
    Scheduler["EventBridge"] --&amp;gt; A3["Agent 3&amp;lt;br/&amp;gt;autonomous Lambda"]
    A3 --&amp;gt;|"A2A app-only token"| A2
    A2 --&amp;gt;|"Delegated token"| MCP["Microsoft MCP server"]
    R1["AWS role 1"] -.-&amp;gt;|"FIC"| B1["Blueprint 1"]
    R2["AWS role 2"] -.-&amp;gt;|"FIC"| B2["Blueprint 2"]
    R3["AWS role 3"] -.-&amp;gt;|"FIC"| B3["Blueprint 3"]&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The A2A request does not contain a magical statement that one agent trusts another. It contains a normal bearer token for Blueprint 2, and that resource validates normal Microsoft Entra claims. The task is agent-shaped. The trust contract is still OAuth.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Protocol interoperability does not imply authorization equivalence.&lt;/strong&gt; Two callers can speak the same A2A dialect and still belong on opposite sides of a capability boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Separate blueprints make three workload trust roots visible
&lt;/h2&gt;

&lt;p&gt;All three agents belong to the same proof-of-value deployment, but they do not all use the same compute surface. Agent 1 and Agent 2 run in AgentCore Runtime, while Agent 3 runs in Lambda. They could still reference agent identities created from one shared blueprint because &lt;a href="https://learn.microsoft.com/entra/agent-id/agent-identities" rel="noopener noreferrer"&gt;a blueprint is a reusable template for multiple agent identities&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I deliberately did not model them that way.&lt;/p&gt;

&lt;p&gt;Each agent has its own blueprint, its own child agent identity, and its own AWS execution role. Each blueprint has one FIC whose subject is the matching AWS role Amazon Resource Name (ARN). Blueprint 1 trusts &lt;code&gt;ExecutionRoleArn&lt;/code&gt;, Blueprint 2 trusts &lt;code&gt;SpecialistRoleArn&lt;/code&gt;, and Blueprint 3 trusts &lt;code&gt;AutonomousRoleArn&lt;/code&gt;, as recorded in the &lt;a href="https://github.com/astaykov/agentid-agentcore/blob/v3.0/docs/entra-setup-guide.md" rel="noopener noreferrer"&gt;Entra setup guide&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Why spend three blueprints where one might function?&lt;/p&gt;

&lt;p&gt;Because the sample is meant to expose the control planes. Microsoft documents that &lt;a href="https://learn.microsoft.com/entra/agent-id/agent-identities" rel="noopener noreferrer"&gt;the blueprint holds the credential while the child agent identity has no credential of its own&lt;/a&gt;. Reusing one blueprint would therefore reuse one credential-bearing parent across three operational roles. That can be a legitimate production design. It is the wrong teaching design when the goal is to inspect three independent workload attestations.&lt;/p&gt;

&lt;p&gt;The separate model makes a one-to-one relationship visible:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;an AWS execution role identifies the running workload;&lt;/li&gt;
&lt;li&gt;a FIC tells one blueprint which AWS role assertion it accepts;&lt;/li&gt;
&lt;li&gt;the blueprint acquires a token for one child agent identity; and&lt;/li&gt;
&lt;li&gt;that agent identity appears as the caller or actor in downstream authorization.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The cost is more Entra objects and more configuration. The benefit is a smaller conceptual and operational blast radius. Rotating, disabling, or misconfiguring Blueprint 3 does not change the credential trust for Agent 2. &lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A shared blueprint groups agents by kind or runtime. A separate blueprint isolates credential ownership and blast radius.&lt;/strong&gt; The right production choice depends on whether those agents are interchangeable instances or independent security principals. For this demonstration, clarity wins.&lt;/p&gt;

&lt;h2&gt;
  
  
  Blueprint 2 is an API surface, not just Agent 2's credential holder
&lt;/h2&gt;

&lt;p&gt;The most important setup step in v3 is easy to misread. Blueprint 2 authenticates Agent 2 through its FIC, but it also represents the resource API that Agent 1 and Agent 3 request tokens for. Microsoft documents that a blueprint receiving incoming requests needs an &lt;a href="https://learn.microsoft.com/entra/agent-id/create-blueprint" rel="noopener noreferrer"&gt;identifier URI and OAuth scope&lt;/a&gt;, just like any other protected web API.&lt;/p&gt;

&lt;p&gt;The sample gives Blueprint 2:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the identifier URI &lt;code&gt;api://{blueprint-2-client-id}&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;access-token version 2;&lt;/li&gt;
&lt;li&gt;delegated scope &lt;code&gt;user_impersonation&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;user role &lt;code&gt;Agent2.Tools.User&lt;/code&gt;; and&lt;/li&gt;
&lt;li&gt;application role &lt;code&gt;Agent2.Chat.Application&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The relevant shape is below. The actual setup uses generated globally unique identifiers for every scope and role.&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;"identifierUris"&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://{blueprint-2-client-id}"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"api"&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="nl"&gt;"requestedAccessTokenVersion"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"oauth2PermissionScopes"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{scope-guid}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"user_impersonation"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"isEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"appRoles"&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="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{user-role-guid}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Agent2.Tools.User"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"allowedMemberTypes"&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;"User"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;span class="nl"&gt;"id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"{application-role-guid}"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"value"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Agent2.Chat.Application"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"allowedMemberTypes"&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;"Application"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="nl"&gt;"isEnabled"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&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;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;Why define both a scope and a user role for the delegated path?&lt;/p&gt;

&lt;p&gt;They answer different questions. The scope says that the client agent has delegated consent to call Blueprint 2 on behalf of a user. The user role says that this user is assigned to the directory-tool capability exposed by Agent 2. Microsoft recommends that a protected API &lt;a href="https://learn.microsoft.com/entra/identity-platform/scenario-protected-web-api-verification-scope-app-roles" rel="noopener noreferrer"&gt;verify scopes for calls made on behalf of users and app roles for application callers&lt;/a&gt;. The same guidance allows a user-assigned app role to add a second claim-based boundary to delegated access.&lt;/p&gt;

&lt;p&gt;The application role answers a third question: may this autonomous principal call Agent 2 without a user? Microsoft Entra emits assigned app-role values in the &lt;code&gt;roles&lt;/code&gt; claim, and &lt;a href="https://learn.microsoft.com/entra/identity-platform/howto-add-app-roles-in-apps" rel="noopener noreferrer"&gt;app roles can be assigned to users, groups, or another application's service principal&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Definitions are not grants. Creating &lt;code&gt;user_impersonation&lt;/code&gt; does not consent Agent Identity 1 to it. Creating &lt;code&gt;Agent2.Tools.User&lt;/code&gt; does not assign a user. Creating &lt;code&gt;Agent2.Chat.Application&lt;/code&gt; does not authorize Agent Identity 3.&lt;/p&gt;

&lt;p&gt;The setup therefore creates three separate grants:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;An &lt;code&gt;oauth2PermissionGrant&lt;/code&gt; from the Agent Identity 1 to the Blueprint 2 resource service principal for &lt;code&gt;user_impersonation&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;A user or group assignment to &lt;code&gt;Agent2.Tools.User&lt;/code&gt; on the Blueprint 2 resource service principal.&lt;/li&gt;
&lt;li&gt;An app-role assignment from Agent Identity 3 to &lt;code&gt;Agent2.Chat.Application&lt;/code&gt; on the Blueprint 2 resource service principal.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Microsoft Graph distinguishes application IDs from service-principal object IDs here. The &lt;a href="https://learn.microsoft.com/graph/api/oauth2permissiongrant-post?view=graph-rest-1.0" rel="noopener noreferrer"&gt;&lt;code&gt;oAuth2PermissionGrant&lt;/code&gt; resource&lt;/a&gt; expects the client and resource service-principal object IDs. The &lt;a href="https://learn.microsoft.com/graph/api/serviceprincipal-post-approleassignedto?view=graph-rest-1.0" rel="noopener noreferrer"&gt;app-role assignment API&lt;/a&gt; likewise uses the principal, resource, and role IDs.&lt;/p&gt;

&lt;p&gt;That distinction is not administrative trivia. It determines which principal receives the claim. A delegated grant on the blueprint principal is not a grant to Agent Identity 1. An application role assigned to Blueprint 3 is not automatically a role assigned to Agent Identity 3. &lt;strong&gt;Authorize the principal that will actually appear in the token exchange.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The delegated path composes client consent, user assignment, and caller identity
&lt;/h2&gt;

&lt;p&gt;The user-delegated path begins before A2A. The browser obtains a token for Blueprint 1 and invokes Agent 1. Agent 1 then needs a new token whose audience is Blueprint 2. Forwarding the browser token would be wrong because an access token is valid only for its intended audience, and Microsoft explicitly warns against relaying &lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow" rel="noopener noreferrer"&gt;middle-tier access tokens to another resource&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Agent 1 first authenticates through Blueprint 1. Its AWS role obtains a short-lived AWS assertion, Blueprint 1's FIC accepts that assertion, and the blueprint obtains the federated managed identity (FMI) token bound to Agent Identity 1. Agent Identity 1 then performs the Microsoft Entra &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-on-behalf-of-oauth-flow" rel="noopener noreferrer"&gt;agent on-behalf-of (OBO) flow&lt;/a&gt; using the user's Blueprint 1 token as the assertion and &lt;code&gt;api://{blueprint-2-client-id}/user_impersonation&lt;/code&gt; as the requested downstream scope.&lt;/p&gt;

&lt;p&gt;The resulting token for Agent 2 carries the user's identity, the delegated scope, the role assigned to that user on Blueprint 2, and the authorized-party identity of Agent Identity 1. Agent 2 validates all of them.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant U as User
    participant A1 as Agent 1
    participant E as Microsoft Entra
    participant A2 as Agent 2
    participant M as MCP server

    U-&amp;gt;&amp;gt;A1: Token for Blueprint 1
    A1-&amp;gt;&amp;gt;E: AWS assertion + Agent 1 FMI/OBO exchange
    E--&amp;gt;&amp;gt;A1: Blueprint 2 token with scp + user role
    A1-&amp;gt;&amp;gt;A2: A2A message/send + bearer token
    A2-&amp;gt;&amp;gt;E: Agent 2 FMI/OBO exchange
    E--&amp;gt;&amp;gt;A2: Delegated MCP token
    A2-&amp;gt;&amp;gt;M: Directory tool call&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;The v3 authorizer does not treat &lt;code&gt;scp&lt;/code&gt; as sufficient. It requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;aud&lt;/code&gt; to identify Blueprint 2;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scp&lt;/code&gt; to contain &lt;code&gt;user_impersonation&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;roles&lt;/code&gt; to contain &lt;code&gt;Agent2.Tools.User&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;azp&lt;/code&gt; to equal Agent Identity 1; and&lt;/li&gt;
&lt;li&gt;the token not to identify itself as an app-only token.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This composition blocks several accidental privilege paths. A user assigned to the role cannot invent a client that lacks delegated consent. A consented client cannot use directory tools for a user who lacks the role. A different client cannot replay an otherwise valid Blueprint 2 token and pass the caller check.&lt;/p&gt;

&lt;p&gt;After that first A2A authorization, Agent 2 still does not forward the Blueprint 2 token to the Microsoft MCP server. It performs another OBO exchange through Blueprint 2 and Agent Identity 2 for the MCP resource scopes. The &lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow" rel="noopener noreferrer"&gt;OBO protocol&lt;/a&gt; exists precisely for this audience transition: each middle tier receives a token for itself, then requests a different token for the next resource.&lt;/p&gt;

&lt;p&gt;There are two authorization boundaries in this chain. Blueprint 2 controls whether the user and Agent 1 may invoke Agent 2's directory skill. The MCP resource controls what Agent 2 may do with the user's delegated authority. &lt;strong&gt;A2A delegation does not erase downstream consent. It adds another resource to the chain.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The autonomous path receives an app role and no inherited tools
&lt;/h2&gt;

&lt;p&gt;Agent 3 has no signed-in user. EventBridge Scheduler invokes its Lambda under AWS controls, then Agent 3 uses its own AWS role, Blueprint 3 FIC, and Agent Identity 3 to request an app-only token for Blueprint 2.&lt;/p&gt;

&lt;p&gt;The Microsoft Entra &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/autonomous-agent-authentication-authorization-flow" rel="noopener noreferrer"&gt;autonomous agent flow&lt;/a&gt; still starts with the blueprint credential and an FMI token for the child agent identity. The second exchange uses client credentials and requests &lt;code&gt;api://{blueprint-2-client-id}/.default&lt;/code&gt;. For client-credentials flows, &lt;a href="https://learn.microsoft.com/entra/identity-platform/scopes-oidc" rel="noopener noreferrer"&gt;&lt;code&gt;.default&lt;/code&gt; resolves the application permissions already granted for that resource&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Agent Identity 3 has exactly one such assignment on Blueprint 2: &lt;code&gt;Agent2.Chat.Application&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;No &lt;code&gt;scp&lt;/code&gt; claim is expected because there is no user delegation. Agent 2 instead requires:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;roles&lt;/code&gt; to contain &lt;code&gt;Agent2.Chat.Application&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;azp&lt;/code&gt; to identify Agent Identity 3;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;oid&lt;/code&gt; to identify Agent Identity 3; and&lt;/li&gt;
&lt;li&gt;the requested skill to be &lt;code&gt;chat&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The sample's &lt;a href="https://github.com/astaykov/agentid-agentcore/blob/v3.0/agent/src/authorization.py" rel="noopener noreferrer"&gt;authorization implementation&lt;/a&gt; reduces the distinction to an explicit branch:&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;if&lt;/span&gt; &lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;scp&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;require_scope&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;user_impersonation&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;require_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent2.Tools.User&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;require_caller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent1_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delegated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
&lt;span class="k"&gt;else&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;require_role&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Agent2.Chat.Application&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;require_caller&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent3_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nf"&gt;require_subject&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claims&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;agent3_id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;application&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;skill&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;directory&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt; &lt;span class="ow"&gt;and&lt;/span&gt; &lt;span class="n"&gt;mode&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;delegated&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="nf"&gt;deny&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The actual code validates the signature with Microsoft Entra signing keys, issuer, audience, expiry, issued-at time, tenant, and claim types before this authorization branch. That follows the protected-API rule that authentication alone is insufficient: &lt;a href="https://learn.microsoft.com/entra/identity-platform/scenario-protected-web-api-verification-scope-app-roles" rel="noopener noreferrer"&gt;the API must verify the expected permission claims&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Notice what is &lt;em&gt;not&lt;/em&gt; in the autonomous path. There is no user role, no delegated scope, and no MCP token exchange. Agent 2 constructs a tool-free agent for &lt;code&gt;chat&lt;/code&gt;; an app-only caller asking for &lt;code&gt;directory&lt;/code&gt; is rejected before a model or tool is invoked. Prompt text cannot promote the caller into another authorization mode because the tool list is selected after claim validation, not by the model.&lt;/p&gt;

&lt;p&gt;This is the useful A2A result. Agent 3 can collaborate with Agent 2 without inheriting the directory authority available to Agent 1's user-delegated path. &lt;strong&gt;Agent-to-agent connectivity is not transitive permission.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Authorization must fail before the model gets a vote
&lt;/h2&gt;

&lt;p&gt;An Agent Card can advertise a skill. That does not mean every authenticated caller may execute it. The &lt;a href="https://a2a-protocol.org/latest/specification/" rel="noopener noreferrer"&gt;A2A specification&lt;/a&gt; deliberately aligns security with standard enterprise web practices, and AgentCore supports OAuth bearer authentication at the runtime boundary. The sample adds the missing resource-server checks inside Agent 2.&lt;/p&gt;

&lt;p&gt;That ordering matters:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AgentCore verifies the inbound JWT configuration before invoking the runtime.&lt;/li&gt;
&lt;li&gt;Agent 2 validates the token signature, issuer, audience, lifetime, and required identity claims.&lt;/li&gt;
&lt;li&gt;Agent 2 distinguishes delegated from application mode.&lt;/li&gt;
&lt;li&gt;Agent 2 checks the scope, role, expected caller, and requested skill.&lt;/li&gt;
&lt;li&gt;Only then does it construct a model with either directory tools or an empty tool list.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;AgentCore currently forwards the A2A bearer token through an allowlisted runtime header so the application can perform those checks. AWS documents that &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-header-allowlist.html" rel="noopener noreferrer"&gt;custom headers can reach the runtime only when allowed by its configuration&lt;/a&gt;, and that the &lt;code&gt;Authorization&lt;/code&gt; header requires a custom JWT authorizer. The sample accepts the AgentCore-compatible forwarded header and never puts the token into the A2A message, Agent Card, prompt, or logs.&lt;/p&gt;

&lt;p&gt;The negative tests are as important as the successful calls. The &lt;a href="https://github.com/astaykov/agentid-agentcore/blob/v3.0/docs/a2a-setup-guide.md" rel="noopener noreferrer"&gt;v3 demonstration guide&lt;/a&gt; checks a user without the Blueprint 2 role, Agent 1 without delegated consent, Agent 3 without its application role, an app-only request for the directory skill, and tokens with the wrong issuer, audience, signature, or lifetime.&lt;/p&gt;

&lt;p&gt;Those failures locate the trust boundary:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;missing consent fails token acquisition;&lt;/li&gt;
&lt;li&gt;missing assignment fails token issuance or resource authorization;&lt;/li&gt;
&lt;li&gt;the wrong caller fails claim authorization;&lt;/li&gt;
&lt;li&gt;the wrong skill fails capability authorization; and&lt;/li&gt;
&lt;li&gt;an invalid token fails authentication.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is no single "A2A authorized" bit. There is a chain of independently testable decisions, and every one happens before a model can interpret the request.&lt;/p&gt;

&lt;p&gt;The first version of this proof of value showed that Entra Agent ID is not limited to Microsoft-hosted agents or Microsoft-hosted resources. The second showed that cross-cloud identity does not require a shared client secret. Version 3 closes the next gap: one governed agent can call another through an open protocol without abandoning scopes, roles, consent, workload attestation, or resource-specific enforcement.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;A2A is not a shortcut around your authorization model. It is where that model becomes visible.&lt;/strong&gt;&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/astaykov/one-identity-two-clouds-an-aws-hosted-agent-that-authorizes-with-microsoft-entra-agent-id-2bkl"&gt;One identity, two clouds: an AWS-hosted agent that authorizes with Microsoft Entra Agent ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://dev.to/astaykov/how-an-aws-hosted-entra-agent-id-drops-the-client-secret-1d4k"&gt;How an AWS-hosted Entra Agent ID drops the client secret&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/astaykov/agentid-agentcore" rel="noopener noreferrer"&gt;astaykov/agentid-agentcore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/astaykov/agentid-agentcore/releases/tag/v3.0" rel="noopener noreferrer"&gt;Release v3.0 of astaykov/agentid-agentcore&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/astaykov/agentid-agentcore/blob/v3.0/docs/a2a-setup-guide.md" rel="noopener noreferrer"&gt;A2A setup guide for release v3.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/astaykov/agentid-agentcore/blob/v3.0/docs/entra-setup-guide.md" rel="noopener noreferrer"&gt;Entra setup guide for release v3.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://github.com/astaykov/agentid-agentcore/blob/v3.0/agent/src/authorization.py" rel="noopener noreferrer"&gt;Authorization implementation for release v3.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://a2a-protocol.org/latest/specification/" rel="noopener noreferrer"&gt;Agent2Agent protocol specification&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/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/en-us/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/en-us/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/identity-platform/scenario-protected-web-api-verification-scope-app-roles" rel="noopener noreferrer"&gt;Protected web API: Verify scopes and app roles&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/howto-add-app-roles-in-apps" rel="noopener noreferrer"&gt;Add app roles to your application and receive them in the token&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/scopes-oidc" rel="noopener noreferrer"&gt;Scopes and permissions in the Microsoft identity platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/api/oauth2permissiongrant-post?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Create an oAuth2PermissionGrant&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/api/serviceprincipal-post-approleassignedto?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Grant an appRoleAssignment for a service principal&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-a2a.html" rel="noopener noreferrer"&gt;Deploy A2A servers in AgentCore Runtime&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-a2a-protocol-contract.html" rel="noopener noreferrer"&gt;A2A protocol contract&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/runtime-header-allowlist.html" rel="noopener noreferrer"&gt;Pass custom headers to Amazon Bedrock AgentCore Runtime&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>entraagentid</category>
      <category>agenttoagent</category>
      <category>aws</category>
      <category>cloudsecurity</category>
    </item>
    <item>
      <title>Encryption in transit is not end-to-end encryption: block unsanctioned GenAI access</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 22 Sep 2026 10:33:00 +0000</pubDate>
      <link>https://dev.to/astaykov/encryption-in-transit-is-not-end-to-end-encryption-block-unsanctioned-genai-access-5a3j</link>
      <guid>https://dev.to/astaykov/encryption-in-transit-is-not-end-to-end-encryption-block-unsanctioned-genai-access-5a3j</guid>
      <description>&lt;h2&gt;
  
  
  Encryption in transit is not end-to-end encryption
&lt;/h2&gt;

&lt;p&gt;The distinction is simple and it matters more in GenAI than in most other areas of technology. TLS protects a connection while data is moving across a network segment. It does not mean the application data is protected from every intermediary that handles the request. A reverse proxy, gateway, WAF, or API layer can terminate TLS, inspect the payload, and then forward it onward. That is still encryption in transit. It is not end-to-end encryption.&lt;/p&gt;

&lt;p&gt;This is the real security question behind the AI risk conversation. If an employee pastes a secret into a third-party AI assistant, the data leaves the trust boundary of the organization. At that point, the provider, the proxy, and any downstream service can observe, store, or process it. The issue is not whether the connection was protected while it crossed the public Internet. The issue is whether the organization still controls the data lifecycle.&lt;/p&gt;

&lt;p&gt;NIST describes TLS as protecting data during electronic dissemination across the Internet in the classic sense, and that is accurate. But it is not the same as saying the data remains protected from every system that participates in the request path. &lt;a href="https://csrc.nist.gov/pubs/sp/800-52/r2/final" rel="noopener noreferrer"&gt;NIST SP 800-52 Revision 2&lt;/a&gt; is careful about transport security, not end-to-end application protection.&lt;/p&gt;

&lt;p&gt;There is a quiet assumption baked into many security conversations, and it usually goes unspoken: if the data is encrypted on the wire, the problem is solved. It is not. The network path can be encrypted on every hop while the gateway still sees the plaintext. That is the core distinction.&lt;/p&gt;

&lt;p&gt;The common gateway topology looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant Client as Browser/App
    participant Gateway
    participant Backend
    Client-&amp;gt;&amp;gt;Gateway: TLS connection 1
    Note over Gateway: TLS terminates here&amp;lt;br/&amp;gt;plaintext exists here
    Gateway-&amp;gt;&amp;gt;Backend: TLS connection 2&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;That diagram has two separate cryptographic relationships. The browser is not cryptographically protected all the way to the backend service. It is protected to the gateway, and then the gateway creates a second protected connection to the backend.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;TLS connection 1:
Browser/App &amp;lt;=================&amp;gt; Gateway

TLS connection 2:
Gateway &amp;lt;=================&amp;gt; Backend
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is a valid transport-security architecture. It is not an end-to-end content-protection architecture.&lt;/p&gt;

&lt;h2&gt;
  
  
  True end-to-end encryption protects the payload from the gateway
&lt;/h2&gt;

&lt;p&gt;End-to-end encryption changes the location of the cryptographic boundary. The gateway can still route the request, enforce some policy, and observe metadata. It cannot decrypt the protected application payload.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant Client as Browser/App
    participant Gateway
    participant Backend as Backend Service
    Client-&amp;gt;&amp;gt;Client: encrypt(payload, backend key)
    Client-&amp;gt;&amp;gt;Gateway: TLS plus encrypted payload
    Note over Gateway: Gateway transports ciphertext&amp;lt;br/&amp;gt;Gateway cannot decrypt
    Gateway-&amp;gt;&amp;gt;Backend: TLS plus encrypted payload
    Backend-&amp;gt;&amp;gt;Backend: decrypt(payload)&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;In that model, the application content is protected before it reaches the gateway. The gateway sees an HTTP request, but the sensitive body is opaque unless it has the backend-controlled decryption material.&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;"payload"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"BASE64_ENCRYPTED_BLOB..."&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 gateway might see routing data and headers like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;POST /api/messages
Host: service.example
Authorization: ...
Content-Type: application/octet-stream

8gh29HFn4k...     &amp;lt;-- opaque encrypted content
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That distinction is not academic. It determines whether the gateway is a reader of the data or a carrier of ciphertext.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Architecture&lt;/th&gt;
&lt;th&gt;Data encrypted on wire?&lt;/th&gt;
&lt;th&gt;Gateway sees plaintext?&lt;/th&gt;
&lt;th&gt;E2E encrypted?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Client -&amp;gt; TLS gateway -&amp;gt; HTTP backend&lt;/td&gt;
&lt;td&gt;Partially&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client -&amp;gt; TLS gateway -&amp;gt; TLS backend&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;No&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Client encrypts payload -&amp;gt; gateway -&amp;gt; backend decrypts&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;&lt;strong&gt;Yes&lt;/strong&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  The gateway is the control plane, not just a door
&lt;/h2&gt;

&lt;p&gt;This is where the real security story starts. A gateway is not merely a convenience layer between users and services. It is often the central choke point for policy enforcement, authentication, logging, routing, and transformation. In enterprise architecture, that makes it a high-value concentration point for sensitive data and a privileged path to the Internet.&lt;/p&gt;

&lt;p&gt;The design pattern is common across Azure and enterprise stacks. An application gateway, API gateway, or reverse proxy does not just pass traffic. It terminates connections, reads headers, inspects payloads, enforces policies, and often writes transaction metadata to logging, monitoring, and analytics systems. &lt;a href="https://learn.microsoft.com/en-us/azure/application-gateway/application-gateway-tcp-tls-overview" rel="noopener noreferrer"&gt;Azure Application Gateway TCP/TLS proxy overview&lt;/a&gt; describes exactly this behavior: the gateway acts as a reverse proxy and can proxy TCP and TLS traffic as part of the service architecture. In API management terms, the platform sits in front of backend APIs and shapes how traffic moves and is governed. &lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-key-concepts" rel="noopener noreferrer"&gt;What is Azure API Management?&lt;/a&gt; describes the gateway as an operational control layer, not just a network convenience.&lt;/p&gt;

&lt;p&gt;That matters because gateway functionality is never isolated from data exposure. The same component that handles TLS, routing, and policy enforcement also sees the application content unless the design explicitly encrypts beyond that boundary. If the gateway is visible to the enterprise as a single control point, then the enterprise also has to treat it as a single blast-radius concentration point.&lt;/p&gt;

&lt;p&gt;This is the part many organizations miss. Endpoint controls are designed for the client devices, not for the central trust boundary that sits between the organization and the Internet. The endpoint can be managed, patched, and monitored. The gateway is different. It is a shared service used by many applications and many users. It often sits near the edge of the trust boundary, and it often has deep visibility into the same data that the browser or mobile app sends.&lt;/p&gt;

&lt;p&gt;That makes the gateway a strategic security object. It is both:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;a concentration point for highly valuable company information, because it sees data from many users, workloads, and applications, and&lt;/li&gt;
&lt;li&gt;a gateway to the Internet, because it provides the path that traffic uses to leave the enterprise boundary.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you treat the gateway as purely an infrastructure detail, you miss the real architectural risk. You are treating a privileged control plane as if it were just a traffic relay.&lt;/p&gt;

&lt;h2&gt;
  
  
  The trust boundary is the thing that matters
&lt;/h2&gt;

&lt;p&gt;The key concept here is the trust boundary. A prompt sent to a public AI assistant crosses the enterprise trust boundary whether or not the network path is encrypted. In many cases, the user is not just calling a service. They are handing their data to a third party and the provider decides what it does with the data next. The organization may not control the log retention, model training settings, contract terms, or retention policy.&lt;/p&gt;

&lt;p&gt;The same architectural reality appears in an internal enterprise API gateway. If a backend service has to see customer data to do its job, then the gateway is an important intermediary but not the authoritative security boundary. The real security question is where the data becomes visible and who can access it.&lt;/p&gt;

&lt;p&gt;This is exactly why the phrase "encrypted in transit" is not enough. It says the data is protected while traveling between endpoints. It does not say the data is protected from the endpoints themselves. A TLS connection between browser and gateway and then between gateway and backend can be secure and still leave the gateway in the clear. The gateway can observe the plaintext, and that is enough to change the risk model.&lt;/p&gt;

&lt;p&gt;The honest answer is that you do not need a single broken protocol to create a dangerous exposure pattern. You need a better boundary model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why the GenAI risk is different
&lt;/h2&gt;

&lt;p&gt;The GenAI category widens that problem because it combines two factors that are easy to ignore when taken separately:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;employees are under pressure to move quickly and show productivity, and&lt;/li&gt;
&lt;li&gt;the AI assistant experience is designed to feel like a normal consumer product, not a corporate system with controls.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That combination is toxic. People paste code, customer data, pricing files, internal proposals, and architecture notes into tools that are not part of the approved enterprise trust domain. Sometimes it is a free tier. Sometimes it is a personal subscription. Sometimes it is because the company license is exhausted or the app is simply more convenient. Sometimes it is because the employee is trying to solve a real business problem under pressure and there is no policy or review path.&lt;/p&gt;

&lt;p&gt;This problem is not theoretical. The recent AI supply-chain and exposure stories show the broader pattern: the path to an AI model is no longer just an application call. It is a software supply chain, a routing layer, a credential boundary, and sometimes a logging sink. The article about the LiteLLM compromise describes a malicious package supply-chain story in which compromised open-source tooling resulted in secrets theft and credential exposure from CI/CD and runtime environments. The investigation is presented as a real-world supply-chain incident, with the article citing forensic research from Snyk, Trend Micro, and Cycode and describing the malicious package behavior in detail. &lt;a href="https://www.infostealers.com/article/largest-ai-supply-chain-breach-of-2026-litellm-hack-impacts-thousands-of-global-enterprises-claim-your-ethical-disclosure/" rel="noopener noreferrer"&gt;InfoStealers on the LiteLLM incident&lt;/a&gt; and the linked Snyk, Trend Micro, and Cycode coverage point to the same risk class: if a tool sits in the path to the model, it becomes part of the exposure surface.&lt;/p&gt;

&lt;p&gt;The Pandaily story about a reported 6 TB LLM router log data exposure should be treated carefully. It is not the same as a definitive forensic closure on every claim, but it reinforces the same basic truth: AI router layers, model gateways, and data processing intermediaries are becoming valuable operational targets. &lt;a href="https://pandaily.com/china-llm-router-logs-6tb-credential-leak-researcher-claim" rel="noopener noreferrer"&gt;Pandaily claim on LLM router logs&lt;/a&gt; is a useful cautionary example even if the final attribution and scope remain contested.&lt;/p&gt;

&lt;p&gt;The broader point is not that every AI service is malicious. The point is that once a prompt, file, or API key crosses into an unvetted service, the organization no longer controls the trust boundary. The data becomes part of a new processing environment with a different contract, a different logging model, and a different risk posture.&lt;/p&gt;

&lt;p&gt;This is why the enterprise question is not "does this service use TLS?" It is "what is the data-handling contract and where is the trust boundary?"&lt;/p&gt;

&lt;h2&gt;
  
  
  Strong AI governance already has an identity plane
&lt;/h2&gt;

&lt;p&gt;The governance answer is not only "block the app." It is also "move the control plane to the identity layer." Microsoft Entra Global Secure Access gives organizations a cloud-delivered security service edge that can enforce policy around private and internet access based on identity-aware controls, rather than relying only on endpoint posture. &lt;a href="https://learn.microsoft.com/en-us/entra/global-secure-access/overview" rel="noopener noreferrer"&gt;What is Global Secure Access?&lt;/a&gt; frames the problem in exactly this way: the modern workforce needs an identity-aware, cloud-delivered network perimeter.&lt;/p&gt;

&lt;p&gt;That matters because AI governance is not just a DLP conversation. It is also an access-governance conversation. &lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-overview" rel="noopener noreferrer"&gt;What is entitlement management?&lt;/a&gt; describes how organizations can automate access request workflows, assignments, reviews, and expiration across apps, groups, and SharePoint resources. Governance fails when access is granted on an ad hoc basis and never reviewed. Entitlement management is how you turn AI access into a time-bound, auditable trust decision.&lt;/p&gt;

&lt;p&gt;And the last layer is user authentication. If the employee is authenticating with a phishing-resistant method such as a passkey, Face ID, or Windows Hello, the enterprise is materially reducing the chance that the decision to use a third-party AI tool is driven by a credential theft event. &lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-passwordless" rel="noopener noreferrer"&gt;Passwordless authentication in Microsoft Entra&lt;/a&gt; is the modern model here: secure, modern credentials reduce the risk that a weak human factor becomes the path into an unauthorized AI workflow.&lt;/p&gt;

&lt;p&gt;You do not solve unsanctioned GenAI risk by adding one more checkbox to a policy page. You solve it by combining: a real trust boundary, policy enforcement at the edge, time-bound access, and phishing-resistant identity. That is how you turn a convenience problem into a governed control plane.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real answer is not a policy slogan. It is a system design rule
&lt;/h2&gt;

&lt;p&gt;Most organizations already know the first half of this story. They know they should not permit employees to dump customer data into a random public AI tool. The harder question is how to make that true in practice without breaking productivity.&lt;/p&gt;

&lt;p&gt;The answer is simple but not trivial: build an approved AI path and block the rest.&lt;/p&gt;

&lt;p&gt;That means a clear policy that treats unsanctioned GenAI like a data exfiltration control issue, not an employee-choice convenience issue. It means approved enterprise services with explicit contracts, data handling statements, and retention boundaries. It means DLP rules that detect prompts, attachments, and API keys. It means browser and network controls that prevent direct access to unapproved AI endpoints. It means a governance process that makes it easy to use the sanctioned tool and hard to use the unsanctioned one.&lt;/p&gt;

&lt;p&gt;This is also where the gateway again becomes central. If the gateway is the high-value choke point, then the policy should be enforced there. If an organization treats the gateway as a neutral network device, it will miss the operational fact that the gateway is one of the highest-value security assets in the environment. The gateway is where you can identify unauthorized model traffic, inspect prompts in transit, block exfiltration, and enforce control-plane policy at the edge. That is not a minor operational detail. It is a foundational design decision.&lt;/p&gt;

&lt;p&gt;The issue is not that "maybe one employee clicked the wrong thing." It is that the gateway is already the place where a lot of sensitive information is concentrated. Endpoint controls do not apply to the gateway. Endpoint controls do not protect the shared trust boundary. They protect the device. The gateway is not the device. The gateway is the enterprise chokepoint.&lt;/p&gt;

&lt;p&gt;This is why the right default posture is to ask a sharper question before approving a new AI use case:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;what data crosses the enterprise boundary?&lt;/li&gt;
&lt;li&gt;which service receives it?&lt;/li&gt;
&lt;li&gt;what logging and retention obligations are in force?&lt;/li&gt;
&lt;li&gt;what downstream processing and model training settings apply?&lt;/li&gt;
&lt;li&gt;what is the contractual and legal boundary for the data?&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If the answers are vague, the answer is no. If the service is not part of the approved enterprise trust domain, it should not receive business data.&lt;/p&gt;

&lt;h2&gt;
  
  
  And that means the governance question is not about the browser. It is about the trust contract
&lt;/h2&gt;

&lt;p&gt;A lot of AI governance conversations still focus on the user experience. They ask whether a feature is easy to use, whether employees can paste a prompt into a chat box, and whether the assistant is helpful. That is not the right security frame. The question is whether the organization is handing data to a service that it can trust, monitor, and govern.&lt;/p&gt;

&lt;p&gt;That is the point behind the Azure AI guidance: approved enterprise services are designed around data boundaries and customer data handling commitments. &lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/responsible-ai/openai/data-privacy" rel="noopener noreferrer"&gt;Data, privacy, and security for Models sold by Azure in Microsoft Foundry&lt;/a&gt; lays out the important contract: Microsoft explicitly states that Azure OpenAI does not use customer data to retrain models for the foundation model service, which is a materially different security and governance posture from a personal or unsanctioned AI app.&lt;/p&gt;

&lt;p&gt;The organization is not just buying a productivity feature. It is buying a trust contract. A service that does not provide a clear data-handling boundary cannot be treated as a sanctioned enterprise AI tool just because it is convenient.&lt;/p&gt;

&lt;p&gt;The policy implication is direct. If you do not have a clear control plane, a policy boundary, and a trusted data-handling contract for the AI service, then the enterprise should not allow business data into it. That is the architectural rule. The app might be encrypted. The gateway might be protected. The logs might be clean. But if the organization does not control the data lifecycle, it does not control the risk.&lt;/p&gt;

&lt;p&gt;The gateway is a privileged point in your architecture. You should design around it as if it were a crown jewel. If it sits on an unsanctioned AI path, the enterprise data is already outside the trust boundary. That is not a training issue. It is a governance issue.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://csrc.nist.gov/pubs/sp/800-52/r2/final" rel="noopener noreferrer"&gt;NIST SP 800-52 Revision 2&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/global-secure-access/overview" rel="noopener noreferrer"&gt;What is Global Secure Access?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/id-governance/entitlement-management-overview" rel="noopener noreferrer"&gt;What is entitlement management?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/authentication/concept-authentication-passwordless" rel="noopener noreferrer"&gt;Passwordless authentication in Microsoft Entra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/application-gateway/application-gateway-tcp-tls-overview" rel="noopener noreferrer"&gt;Azure Application Gateway TCP/TLS proxy overview&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/api-management/api-management-key-concepts" rel="noopener noreferrer"&gt;What is Azure API Management?&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/ai-foundry/responsible-ai/openai/data-privacy" rel="noopener noreferrer"&gt;Data, privacy, and security for Models sold by Azure in Microsoft Foundry&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.infostealers.com/article/largest-ai-supply-chain-breach-of-2026-litellm-hack-impacts-thousands-of-global-enterprises-claim-your-ethical-disclosure/" rel="noopener noreferrer"&gt;Largest AI Supply Chain Breach of 2026: LiteLLM Hack Impacts Thousands of Global Enterprises&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://snyk.io/blog/poisoned-security-scanner-backdooring-litellm/" rel="noopener noreferrer"&gt;Snyk: Poisoned security scanner backdooring LiteLLM&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.trendmicro.com/en_us/research/26/c/inside-litellm-supply-chain-compromise.html" rel="noopener noreferrer"&gt;Trend Micro: Inside the LiteLLM supply-chain compromise&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://cycode.com/blog/lite-llm-supply-chain-attack/" rel="noopener noreferrer"&gt;Cycode: LiteLLM supply-chain attack&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pandaily.com/china-llm-router-logs-6tb-credential-leak-researcher-claim" rel="noopener noreferrer"&gt;Pandaily: Researcher claims 6TB China LLM router logs exposed enterprise credentials&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>privacy</category>
      <category>azure</category>
    </item>
    <item>
      <title>How an AWS-hosted Entra Agent ID drops the client secret</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Wed, 16 Sep 2026 10:30:00 +0000</pubDate>
      <link>https://dev.to/astaykov/how-an-aws-hosted-entra-agent-id-drops-the-client-secret-1d4k</link>
      <guid>https://dev.to/astaykov/how-an-aws-hosted-entra-agent-id-drops-the-client-secret-1d4k</guid>
      <description>&lt;p&gt;After I published &lt;a href="https://dev.to/astaykov/one-identity-two-clouds-an-aws-hosted-agent-that-authorizes-with-microsoft-entra-agent-id-2bkl"&gt;One identity, two clouds: an AWS-hosted agent that authorizes with Microsoft Entra Agent ID&lt;/a&gt;, most of the follow-up questions were not about the cross-cloud authorization model.&lt;/p&gt;

&lt;p&gt;They were about the client secret.&lt;/p&gt;

&lt;p&gt;Where should it be stored? How should it be rotated? Should it live in AWS Secrets Manager? What happens when it expires?&lt;/p&gt;

&lt;p&gt;Those are sensible production questions. They were also questions about a demonstration compromise, not the architecture I wanted people to take away. The point of the sample was to show that an agent hosted in AWS can carry a governed Microsoft Entra Agent ID. The secret was plumbing used to keep the first proof of concept focused.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/astaykov/agentid-agentcore/releases/tag/v2.0" rel="noopener noreferrer"&gt;Release v2.0 of the sample&lt;/a&gt; removes that distraction. The agent now uses &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; with &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_oidc_outbound.html" rel="noopener noreferrer"&gt;AWS IAM Outbound Identity Federation&lt;/a&gt;. AWS attests to the workload with a short-lived JSON Web Token (JWT). Microsoft Entra accepts that JWT only when it matches an explicitly configured trust relationship.&lt;/p&gt;

&lt;p&gt;There is no Entra client secret in the runtime path.&lt;/p&gt;

&lt;p&gt;The interesting part is not that a secret moved somewhere safer. It is that the two clouds stopped sharing one.&lt;/p&gt;

&lt;h2&gt;
  
  
  The client secret was never the identity
&lt;/h2&gt;

&lt;p&gt;An Agent ID blueprint needs to authenticate before it can acquire tokens for the agent identities it governs. Microsoft documents federated identity credentials, certificates, and client secrets as supported blueprint credential types, while the agent identity itself does not hold a credential of its own (&lt;a href="https://learn.microsoft.com/entra/agent-id/agent-identities#authorizing-agent-identities" rel="noopener noreferrer"&gt;Agent identities in Microsoft Entra Agent ID&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The first version authenticated the blueprint with a client secret. That proved the Agent ID token flow, but it also introduced a long-lived value with the usual lifecycle:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Generate it in Microsoft Entra.&lt;/li&gt;
&lt;li&gt;transport it to AWS;&lt;/li&gt;
&lt;li&gt;protect it at rest and in deployment;&lt;/li&gt;
&lt;li&gt;expose it to the process that needs it;&lt;/li&gt;
&lt;li&gt;rotate it before expiry; and&lt;/li&gt;
&lt;li&gt;coordinate the old and new values without interrupting the agent.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;A capable secret manager improves several of those steps. It does not remove them. &lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation#why-use-workload-identity-federation" rel="noopener noreferrer"&gt;Microsoft's workload identity federation guidance&lt;/a&gt; makes the recommendation explicit: configure Entra to trust tokens from the workload's external identity provider, then exchange those tokens for Microsoft identity platform access tokens.&lt;/p&gt;

&lt;p&gt;For this sample, the external identity provider is not another directory bolted onto the design. It is AWS itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  What version 2.0 changes
&lt;/h2&gt;

&lt;p&gt;The core agent architecture remains the same. The agent still runs in &lt;a href="https://docs.aws.amazon.com/bedrock-agentcore/latest/devguide/agents-tools-runtime.html" rel="noopener noreferrer"&gt;Amazon Bedrock AgentCore Runtime&lt;/a&gt;, still has a Microsoft Entra Agent ID, and still obtains resource-specific tokens for downstream calls.&lt;/p&gt;

&lt;p&gt;Only the blueprint authentication step changes:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Before, the AgentCore process presented an Entra client secret.&lt;/li&gt;
&lt;li&gt;Now, the process asks AWS Security Token Service for a short-lived web identity token representing its IAM role.&lt;/li&gt;
&lt;li&gt;Microsoft Entra validates that AWS token against a federated identity credential (FIC) configured on the Agent ID blueprint.&lt;/li&gt;
&lt;li&gt;After that validation, the established Agent ID blueprint-to-agent token flow continues.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Microsoft now lists AWS workloads using IAM Outbound Identity Federation as a supported workload identity federation scenario (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation#supported-scenarios" rel="noopener noreferrer"&gt;Workload identity federation concepts&lt;/a&gt;). AWS documents the corresponding &lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_oidc_outbound.html" rel="noopener noreferrer"&gt;outbound identity federation setup&lt;/a&gt; and the AWS Security Token Service (STS) &lt;a href="https://docs.aws.amazon.com/STS/latest/APIReference/API_GetWebIdentityToken.html" rel="noopener noreferrer"&gt;&lt;code&gt;GetWebIdentityToken&lt;/code&gt; API&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The flow looks like this:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant Agent as AgentCore runtime
    participant STS as AWS STS
    participant Entra as Microsoft Entra token endpoint
    participant API as Downstream API

    Agent-&amp;gt;&amp;gt;STS: GetWebIdentityToken using the runtime IAM role
    STS--&amp;gt;&amp;gt;Agent: Short-lived AWS-signed JWT
    Agent-&amp;gt;&amp;gt;Entra: Blueprint token request with AWS JWT as client_assertion
    Entra-&amp;gt;&amp;gt;Entra: Match issuer, subject, and audience
    Entra--&amp;gt;&amp;gt;Agent: Blueprint token (T1)
    Agent-&amp;gt;&amp;gt;Entra: Agent token request with T1 as client_assertion
    Entra--&amp;gt;&amp;gt;Agent: Agent identity access token
    Agent-&amp;gt;&amp;gt;API: Call with agent identity access token&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;There are two assertions here, and conflating them hides the actual security model.&lt;/p&gt;

&lt;p&gt;The AWS JWT proves which AWS workload is calling. The blueprint token proves that the authenticated blueprint is allowed to acquire a token for a particular agent identity. Neither token is the final downstream access token.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure AWS to attest to the workload
&lt;/h2&gt;

&lt;p&gt;AWS IAM Outbound Identity Federation lets an IAM principal request an OpenID Connect (OIDC)-compatible token for an external service. The account must first have outbound web identity federation enabled, and the AgentCore runtime role must be allowed to call &lt;code&gt;sts:GetWebIdentityToken&lt;/code&gt; (&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_oidc_outbound.html" rel="noopener noreferrer"&gt;Enabling AWS IAM Outbound Identity Federation&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The permission should constrain what the role can request. In this design, the audience is:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;api://AzureADTokenExchange
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That is the audience Microsoft recommends for an external assertion presented to its token exchange (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust#important-considerations-and-restrictions" rel="noopener noreferrer"&gt;Configure an app to trust an external identity provider&lt;/a&gt;). Restricting the AWS permission to this audience prevents the role from using the same permission to mint arbitrary outbound identity tokens for unrelated relying parties.&lt;/p&gt;

&lt;p&gt;The runtime obtains the assertion from STS:&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="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;

&lt;span class="n"&gt;sts&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;boto3&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;sts&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;sts&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get_web_identity_token&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="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;api://AzureADTokenExchange&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="n"&gt;SigningAlgorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;DurationSeconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="n"&gt;aws_assertion&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WebIdentityToken&lt;/span&gt;&lt;span class="sh"&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 method and response shape are defined by the AWS &lt;a href="https://docs.aws.amazon.com/STS/latest/APIReference/API_GetWebIdentityToken.html" rel="noopener noreferrer"&gt;&lt;code&gt;GetWebIdentityToken&lt;/code&gt; API&lt;/a&gt;. A five-minute lifetime is not a universal requirement, but it keeps the assertion useful only for the immediate exchange and limits the value of a captured token.&lt;/p&gt;

&lt;p&gt;The token has three claims that matter to the Entra trust:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;iss&lt;/code&gt; identifies the account-specific AWS outbound identity federation issuer.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;sub&lt;/code&gt; identifies the AWS IAM principal, in this case the AgentCore runtime role.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;aud&lt;/code&gt; identifies Microsoft Entra's token exchange as the intended recipient.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;AWS signs the token and publishes the metadata Microsoft Entra needs to validate it. The application does not create or sign the JWT itself.&lt;/p&gt;

&lt;h2&gt;
  
  
  Configure Entra to trust one AWS role
&lt;/h2&gt;

&lt;p&gt;The corresponding federated identity credential belongs on the Agent ID blueprint's application registration. It contains the expected issuer, subject, and audience:&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;"agentcore-runtime"&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;account-specific-id&amp;gt;.tokens.sts.global.api.aws"&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;"arn:aws:iam::&amp;lt;account-id&amp;gt;:role/&amp;lt;agentcore-runtime-role&amp;gt;"&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;These are not descriptive labels. Microsoft Entra compares them with the &lt;code&gt;iss&lt;/code&gt;, &lt;code&gt;sub&lt;/code&gt;, and &lt;code&gt;aud&lt;/code&gt; claims in the incoming AWS token. The values must match exactly and case-sensitively (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation#how-it-works" rel="noopener noreferrer"&gt;Workload identity federation concepts&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That &lt;strong&gt;exact&lt;/strong&gt; match is the authorization boundary.&lt;/p&gt;

&lt;p&gt;Do not set the subject to an account-wide wildcard. Federated identity credentials do not support wildcards anyway, and that limitation is useful here (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust#important-considerations-and-restrictions" rel="noopener noreferrer"&gt;Configure an app to trust an external identity provider&lt;/a&gt;). Trust the runtime role that represents this workload. If another role needs the same blueprint, make that an explicit trust decision rather than an accidental side effect of a broad pattern.&lt;/p&gt;

&lt;p&gt;The trust can now be stated without cloud branding:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Accept a token from this issuer, for this subject, intended for this audience.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;That is the part workload identity federation standardizes. Microsoft Entra does not need the AWS access key. AWS does not need the Entra client secret.&lt;/p&gt;

&lt;h2&gt;
  
  
  The most important code is in the MSAL constructors
&lt;/h2&gt;

&lt;p&gt;The raw HTTP requests explain the protocol, but they are not how version 2.0 implements it. The important part of the sample is how it composes two long-lived &lt;a href="https://learn.microsoft.com/python/api/msal/msal.application.confidentialclientapplication?view=msal-py-latest" rel="noopener noreferrer"&gt;&lt;code&gt;msal.ConfidentialClientApplication&lt;/code&gt;&lt;/a&gt; instances with two callable client-assertion providers.&lt;/p&gt;

&lt;p&gt;There are two documentation layers behind this code. The Microsoft Authentication Library (MSAL) team's &lt;a href="https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/How-to-Use-FIC-and-FMI-in-Agentic-Scenarios" rel="noopener noreferrer"&gt;FIC and federated managed identity (FMI) guidance for agentic scenarios&lt;/a&gt; describes the Agent ID-specific blueprint, &lt;code&gt;fmi_path&lt;/code&gt;, blueprint token (T1), and child-agent exchange. The &lt;a href="https://learn.microsoft.com/python/api/msal/msal.application.confidentialclientapplication?view=msal-py-latest" rel="noopener noreferrer"&gt;MSAL Python &lt;code&gt;ConfidentialClientApplication&lt;/code&gt; reference&lt;/a&gt; describes the generic confidential-client constructor and its &lt;code&gt;client_credential&lt;/code&gt; parameter. The v2.0 sample joins those two layers by supplying functions as the client assertions.&lt;/p&gt;

&lt;p&gt;That distinction matters because a client assertion is short-lived. Passing the current JWT as a static string would make the MSAL client depend on an assertion that eventually expires. Passing a function lets MSAL ask for an assertion when it actually needs to send a request to the token endpoint.&lt;/p&gt;

&lt;p&gt;The resulting chain is:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;sequenceDiagram
    participant Agent as Agent CCA
    participant AgentProvider as _agent_assertion()
    participant Blueprint as Blueprint CCA
    participant AwsProvider as _get_blueprint_assertion()
    participant STS as AWS STS

    Agent-&amp;gt;&amp;gt;AgentProvider: Request client assertion
    AgentProvider-&amp;gt;&amp;gt;Blueprint: Acquire T1 for fmi_path
    Blueprint-&amp;gt;&amp;gt;AwsProvider: Request client assertion
    AwsProvider-&amp;gt;&amp;gt;STS: GetWebIdentityToken
    STS--&amp;gt;&amp;gt;AwsProvider: Fresh AWS JWT
    AwsProvider--&amp;gt;&amp;gt;Blueprint: AWS client assertion
    Blueprint-&amp;gt;&amp;gt;Blueprint: Exchange AWS JWT for T1
    Blueprint--&amp;gt;&amp;gt;AgentProvider: T1
    AgentProvider--&amp;gt;&amp;gt;Agent: T1 client assertion
    Agent-&amp;gt;&amp;gt;Agent: Continue OBO request&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;There is no special MSAL class for "AWS-hosted Agent ID." Both objects are normal confidential clients. Their &lt;code&gt;client_credential&lt;/code&gt; configuration delegates assertion acquisition to application code.&lt;/p&gt;

&lt;h3&gt;
  
  
  The blueprint confidential client application delegates its credential to AWS STS
&lt;/h3&gt;

&lt;p&gt;The blueprint confidential client application (CCA) is created once per process:&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;_get_blueprint_assertion&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;Notice what is passed as &lt;code&gt;client_assertion&lt;/code&gt;. It is the function object &lt;code&gt;_get_blueprint_assertion&lt;/code&gt;, not the result of calling that function.&lt;/p&gt;

&lt;p&gt;The constructor therefore does not contact AWS STS and does not capture one JWT forever. It stores a callable that can provide the credential later. This extends the generic &lt;a href="https://learn.microsoft.com/python/api/msal/msal.application.confidentialclientapplication?view=msal-py-latest" rel="noopener noreferrer"&gt;&lt;code&gt;client_credential&lt;/code&gt; assertion mechanism&lt;/a&gt; with a provider backed by AWS. In the &lt;a href="https://github.com/astaykov/agentid-agentcore/blob/v2.0/agent/src/agent.py" rel="noopener noreferrer"&gt;v2.0 implementation&lt;/a&gt;, that provider asks STS for a five-minute assertion:&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;_get_blueprint_assertion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_ensure_sts_client&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;get_web_identity_token&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;FIC_AUDIENCE&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="n"&gt;SigningAlgorithm&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;RS256&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="n"&gt;DurationSeconds&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="mi"&gt;300&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;response&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;WebIdentityToken&lt;/span&gt;&lt;span class="sh"&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 invokes the provider when it needs a client assertion for an Entra token-endpoint request. The application deliberately does not add another cache around the AWS JWT. MSAL caches the Entra token produced by the exchange, while a later token-endpoint request can obtain a fresh AWS assertion.&lt;/p&gt;

&lt;p&gt;The &lt;code&gt;*args, **kwargs&lt;/code&gt; signature is intentional. The sample tolerates MSAL Python versions that invoke the assertion provider with no arguments and versions that provide assertion context. The provider does not need that context because its issuer, audience, signing algorithm, and lifetime are fixed by the workload-federation configuration.&lt;/p&gt;

&lt;p&gt;This is the first delegation point:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When the blueprint client needs to authenticate, delegate credential production to AWS STS.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The blueprint remains the confidential client from Entra's perspective. AWS STS is simply the component that supplies its short-lived proof.&lt;/p&gt;

&lt;h3&gt;
  
  
  The agent client delegates its assertion to the blueprint client
&lt;/h3&gt;

&lt;p&gt;The second constructor repeats the pattern at the next identity boundary:&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;_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_IDENTITY_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;_agent_assertion&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;This client represents the child Agent ID. It cannot ask AWS STS for its client assertion directly because AWS attests to the runtime role, while the Entra FIC is configured on the blueprint. The agent client's assertion must be T1, the token that proves the blueprint-to-agent relationship.&lt;/p&gt;

&lt;p&gt;Its provider therefore delegates back to the persistent blueprint client:&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;_agent_assertion&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;*&lt;/span&gt;&lt;span class="n"&gt;args&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="o"&gt;**&lt;/span&gt;&lt;span class="n"&gt;kwargs&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;blueprint_app&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;_ensure_blueprint_app&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
    &lt;span class="n"&gt;t1_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="n"&gt;scopes&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;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_IDENTITY_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;if&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="ow"&gt;not&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;t1_result&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;raise&lt;/span&gt; &lt;span class="nc"&gt;TokenAcquisitionError&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;FMI Stage 1 failed: {} - {}&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;format&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
                &lt;span class="n"&gt;t1_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&lt;/span&gt;
                &lt;span class="n"&gt;t1_result&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;error_description&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;),&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;t1_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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;fmi_path&lt;/code&gt; value tells Entra which child Agent ID the blueprint is acquiring T1 for. This is the Agent ID-specific extension described in the MSAL team's &lt;a href="https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/How-to-Use-FIC-and-FMI-in-Agentic-Scenarios" rel="noopener noreferrer"&gt;FIC and FMI agentic-scenario guidance&lt;/a&gt;, not an ordinary resource scope or OAuth OBO parameter. If the blueprint CCA must call the token endpoint, it invokes its own &lt;code&gt;_get_blueprint_assertion&lt;/code&gt; provider and receives a fresh AWS JWT. &lt;code&gt;_agent_assertion&lt;/code&gt; then returns the resulting T1 to the agent CCA.&lt;/p&gt;

&lt;p&gt;This is the second delegation point:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;When the agent client needs to authenticate, delegate assertion production to the blueprint client.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;The two callbacks model the two trust transitions directly in code:&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    OBO["Agent CCA&amp;lt;br/&amp;gt;OBO request"]
    AP["_agent_assertion()"]
    BP["Blueprint CCA&amp;lt;br/&amp;gt;FMI request"]
    AWS["_get_blueprint_assertion()"]
    STS["AWS STS"]

    OBO --&amp;gt;|"needs T1"| AP
    AP --&amp;gt; BP
    BP --&amp;gt;|"needs AWS assertion"| AWS
    AWS --&amp;gt; STS
    STS --&amp;gt;|"AWS JWT"| AWS
    AWS --&amp;gt; BP
    BP --&amp;gt;|"T1"| AP
    AP --&amp;gt; OBO&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;This is dependency injection applied to client authentication. MSAL owns token acquisition, protocol parameters, and its token cache. The application owns how a valid assertion is produced at each boundary.&lt;/p&gt;

&lt;h3&gt;
  
  
  Construct once, refresh through the callbacks
&lt;/h3&gt;

&lt;p&gt;Both confidential clients are lazy-initialized singletons in the container process. That is not just a small optimization.&lt;/p&gt;

&lt;p&gt;Reusing the blueprint CCA preserves its in-memory cache for T1. Reusing the agent CCA preserves its in-memory cache for downstream user tokens. Constructing either CCA for every request would discard the corresponding cache and force avoidable token-endpoint calls.&lt;/p&gt;

&lt;p&gt;The locks around both constructors make the initialization safe when concurrent invocations reach a newly started process:&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;with&lt;/span&gt; &lt;span class="n"&gt;_blueprint_lock&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;_blueprint_app&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The inner check matters because another invocation might have initialized the client while the current invocation waited for the lock.&lt;/p&gt;

&lt;p&gt;The callbacks do not mean AWS STS is called for every tool call. The normal path is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;try the relevant MSAL cache;&lt;/li&gt;
&lt;li&gt;return a usable cached token when one exists;&lt;/li&gt;
&lt;li&gt;invoke the assertion provider only when MSAL needs a token-endpoint request; and&lt;/li&gt;
&lt;li&gt;let that provider produce a current assertion rather than reuse an expired string.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This is why the constructor wiring is the center of the implementation. The code does not contain a scheduled client-secret rotation job because the credential lifecycle has become part of token acquisition itself.&lt;/p&gt;

&lt;h3&gt;
  
  
  Assertion delegation is not user delegation
&lt;/h3&gt;

&lt;p&gt;The word "delegation" can hide two different mechanisms in this sample.&lt;/p&gt;

&lt;p&gt;The callback functions delegate &lt;em&gt;credential production&lt;/em&gt; between Python components. &lt;code&gt;_get_blueprint_assertion&lt;/code&gt; supplies the AWS workload assertion, and &lt;code&gt;_agent_assertion&lt;/code&gt; supplies T1. No user identity is involved in either callback.&lt;/p&gt;

&lt;p&gt;OAuth 2.0 on-behalf-of (OBO) user delegation happens later, when the persistent agent CCA calls:&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;tr_result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;agent_app&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;acquire_token_on_behalf_of&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;user_assertion&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;inbound_user_token&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="n"&gt;scopes&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;scopes&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;At that point MSAL has two different inputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the agent CCA's callable &lt;code&gt;client_assertion&lt;/code&gt;, which produces T1 and authenticates the Agent ID; and&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;user_assertion&lt;/code&gt;, which is the inbound user token and carries the delegated user context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;T1 answers, "Which agent is making this request, and may this blueprint act for it?" The user assertion answers, "On behalf of which signed-in user is the agent acting?" The final resource token combines those parts of the flow for the requested downstream scope.&lt;/p&gt;

&lt;p&gt;Keeping these inputs separate is what makes the sample useful. The AWS workload assertion is not treated as a user token. T1 is not treated as the downstream access token. The inbound user token is not used as the agent credential. Each assertion has one job.&lt;/p&gt;

&lt;h2&gt;
  
  
  Exchange the AWS assertion for the blueprint token
&lt;/h2&gt;

&lt;p&gt;Underneath those MSAL constructors, the AgentCore runtime presents the AWS JWT to the Microsoft identity platform token endpoint as a client assertion. This is the &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;client credentials flow with a federated credential&lt;/a&gt;, not a custom token format or a vendor-specific backchannel.&lt;/p&gt;

&lt;p&gt;For an Agent ID blueprint, the first request also identifies the target agent identity through &lt;code&gt;fmi_path&lt;/code&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/&amp;lt;tenant-id&amp;gt;/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id=&amp;lt;agent-blueprint-client-id&amp;gt;
&amp;amp;scope=api://AzureADTokenExchange/.default
&amp;amp;grant_type=client_credentials
&amp;amp;client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&amp;amp;client_assertion=&amp;lt;aws-sts-jwt&amp;gt;
&amp;amp;fmi_path=&amp;lt;agent-identity-client-id&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Microsoft documents the &lt;code&gt;fmi_path&lt;/code&gt; parameter as the instruction that tells the blueprint which agent identity it is acting for (&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;Authenticate and acquire tokens for autonomous agents&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;If the AWS token's signature is valid and its issuer, subject, and audience match the federated identity credential, Entra returns the blueprint token, often called T1 in the Agent ID flow.&lt;/p&gt;

&lt;p&gt;That is where the workload federation step ends.&lt;/p&gt;

&lt;h2&gt;
  
  
  Continue with the Agent ID token exchange
&lt;/h2&gt;

&lt;p&gt;T1 is not the token the agent sends to Microsoft Graph, an MCP server, or another protected API. The runtime uses T1 as the client assertion in a second request, this time as the agent identity:&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/&amp;lt;tenant-id&amp;gt;/oauth2/v2.0/token
Content-Type: application/x-www-form-urlencoded

client_id=&amp;lt;agent-identity-client-id&amp;gt;
&amp;amp;scope=&amp;lt;downstream-resource&amp;gt;/.default
&amp;amp;grant_type=client_credentials
&amp;amp;client_assertion_type=urn:ietf:params:oauth:client-assertion-type:jwt-bearer
&amp;amp;client_assertion=&amp;lt;agent-blueprint-token-T1&amp;gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That second request is the documented &lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow#request-an-agent-identity-token" rel="noopener noreferrer"&gt;agent identity token request&lt;/a&gt;. The resulting access token represents the Agent ID and is scoped to the downstream resource.&lt;/p&gt;

&lt;p&gt;This separation matters operationally:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;AWS proves the runtime role to the blueprint.&lt;/li&gt;
&lt;li&gt;The blueprint proves its authority to act for the child agent identity.&lt;/li&gt;
&lt;li&gt;Microsoft Entra issues a resource-specific token for that agent identity.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Workload identity federation replaces the blueprint credential. It does not flatten the Agent ID parent-child model, bypass consent, or grant the agent new downstream permissions. The agent still needs the appropriate application permissions and administrator authorization described in the &lt;a href="https://learn.microsoft.com/entra/agent-id/autonomous-agent-authentication-authorization-flow#grant-application-permissions" rel="noopener noreferrer"&gt;autonomous agent authorization flow&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Authentication proves which workload and which agent are involved. Authorization still decides what that agent may do.&lt;/p&gt;

&lt;h2&gt;
  
  
  What became safer, and what did not
&lt;/h2&gt;

&lt;p&gt;The most visible gain is the removal of a long-lived Entra client secret from the AWS runtime. There is no secret value to copy into deployment configuration, retrieve from a secret store, rotate on a calendar, or accidentally leave valid after the workload changes.&lt;/p&gt;

&lt;p&gt;The assertion is also bound to the AWS identity that requested it. Microsoft Entra accepts it because the configured subject identifies the expected IAM role, not because the bearer knows a shared string.&lt;/p&gt;

&lt;p&gt;But "secretless" should not be read as "trustless" or "credential-free."&lt;/p&gt;

&lt;p&gt;The AgentCore runtime's access to its IAM role is now the root of trust. A process that can run as that role and call &lt;code&gt;GetWebIdentityToken&lt;/code&gt; can request the same AWS assertion. The AWS role assignment, its trust policy, the permission to call STS, and the audience and duration conditions therefore need the same review you would give any production identity boundary.&lt;/p&gt;

&lt;p&gt;Short-lived tokens also remain bearer material while they are valid. Do not log the AWS assertion, T1, or the final access token. Do not pass them into model context or tool output. Cache them only for their useful lifetime and keep token acquisition in the trusted application layer.&lt;/p&gt;

&lt;p&gt;Federation removes an entire secret lifecycle. It does not excuse weak runtime isolation.&lt;/p&gt;

&lt;h2&gt;
  
  
  Failure modes worth testing
&lt;/h2&gt;

&lt;p&gt;The happy path is short. The useful tests are mostly about proving that the trust is narrow.&lt;/p&gt;

&lt;h3&gt;
  
  
  Change the audience
&lt;/h3&gt;

&lt;p&gt;Request an AWS token for an audience other than &lt;code&gt;api://AzureADTokenExchange&lt;/code&gt;. The Entra exchange should fail because the incoming &lt;code&gt;aud&lt;/code&gt; no longer matches the federated identity credential. Audience checking is a required part of the configured trust (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust#important-considerations-and-restrictions" rel="noopener noreferrer"&gt;Configure an app to trust an external identity provider&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Run under another IAM role
&lt;/h3&gt;

&lt;p&gt;Obtain a valid AWS token as a different role. Signature validation should still succeed, but the token exchange should fail because the &lt;code&gt;sub&lt;/code&gt; claim does not match. This test demonstrates the difference between trusting AWS as an issuer and trusting every AWS workload.&lt;/p&gt;

&lt;h3&gt;
  
  
  Point to another AWS issuer
&lt;/h3&gt;

&lt;p&gt;Use a token from another AWS account's outbound identity issuer. The subject might look familiar, but the &lt;code&gt;iss&lt;/code&gt; value will differ. Entra uses the issuer and subject combination to identify the external workload (&lt;a href="https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust#important-considerations-and-restrictions" rel="noopener noreferrer"&gt;Configure an app to trust an external identity provider&lt;/a&gt;).&lt;/p&gt;

&lt;h3&gt;
  
  
  Request a permission the agent does not have
&lt;/h3&gt;

&lt;p&gt;Keep the workload federation valid and ask for a downstream scope or application permission that has not been granted. The Agent ID token request should fail independently of AWS authentication. This is evidence that workload authentication has not bypassed the Agent ID authorization model.&lt;/p&gt;

&lt;h3&gt;
  
  
  Remove &lt;code&gt;sts:GetWebIdentityToken&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;The failure should happen in AWS before any call reaches Microsoft Entra. That gives operators a clean boundary when diagnosing incidents: AWS controls whether the runtime can obtain an assertion, while Entra controls whether that assertion is trusted and what the resulting Agent ID may access.&lt;/p&gt;

&lt;h2&gt;
  
  
  The configuration is the architecture
&lt;/h2&gt;

&lt;p&gt;The code change in version 2.0 is not large. That is exactly why the configuration deserves attention.&lt;/p&gt;

&lt;p&gt;The security properties live in four places:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the IAM role attached to the AgentCore runtime;&lt;/li&gt;
&lt;li&gt;the role's permission and conditions for &lt;code&gt;sts:GetWebIdentityToken&lt;/code&gt;;&lt;/li&gt;
&lt;li&gt;the Entra federated identity credential's exact issuer, subject, and audience; and&lt;/li&gt;
&lt;li&gt;the permissions granted to the resulting Agent ID.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Reviewing only the Python token requests misses three of those four boundaries.&lt;/p&gt;

&lt;p&gt;The first version asked, "Can an AWS-hosted agent authenticate and authorize as a Microsoft Entra Agent ID?" The answer was yes.&lt;/p&gt;

&lt;p&gt;Version 2.0 asks the production question: "Can it do that without distributing a Microsoft credential into AWS?" The answer is also yes.&lt;/p&gt;

&lt;p&gt;AWS attests to the workload it runs. Microsoft Entra decides whether to trust that attestation. The Agent ID blueprint then acquires a token for the child agent identity through its established flow.&lt;/p&gt;

&lt;p&gt;No shared secret crosses the cloud boundary. Trust does.&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/releases/tag/v2.0" rel="noopener noreferrer"&gt;Release v2.0 of astaykov/agentid-agentcore&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-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/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://github.com/AzureAD/microsoft-authentication-library-for-dotnet/wiki/How-to-Use-FIC-and-FMI-in-Agentic-Scenarios" rel="noopener noreferrer"&gt;How to use FIC and FMI in agentic scenarios&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/python/api/msal/msal.application.confidentialclientapplication?view=msal-py-latest" rel="noopener noreferrer"&gt;MSAL Python ConfidentialClientApplication&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/IAM/latest/UserGuide/id_credentials_oidc_outbound.html" rel="noopener noreferrer"&gt;AWS IAM Outbound Identity Federation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.aws.amazon.com/STS/latest/APIReference/API_GetWebIdentityToken.html" rel="noopener noreferrer"&gt;AWS STS GetWebIdentityToken&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>entraagentid</category>
      <category>workloadidentity</category>
      <category>aws</category>
      <category>cloudsecurity</category>
    </item>
    <item>
      <title>Your API gateway is not an identity provider, and promoting it to one is a liability</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 08 Sep 2026 10:37:00 +0000</pubDate>
      <link>https://dev.to/astaykov/your-api-gateway-is-not-an-identity-provider-and-promoting-it-to-one-is-a-liability-3eoh</link>
      <guid>https://dev.to/astaykov/your-api-gateway-is-not-an-identity-provider-and-promoting-it-to-one-is-a-liability-3eoh</guid>
      <description>&lt;p&gt;Nobody disputes that the API gateway belongs at the center of an enterprise architecture. It is the discovery surface, the throttle, the routing layer, the audit point, and the place where a platform team decouples consumers from the services they consume. That is not the problem.&lt;/p&gt;

&lt;p&gt;The problem starts the moment a gateway stops validating tokens and starts issuing them. At that point it is no longer a gateway. It is an identity provider. And in most estates I have looked at, it is not governed, audited, monitored, or operated as one. It is operated as middleware.&lt;/p&gt;

&lt;p&gt;So let me put my position at the top rather than build to it. &lt;strong&gt;It is not the API gateway's job to be an identity provider.&lt;/strong&gt; The gateway does discovery, governance, quota, decoupling, routing, and pre-authorization at the edge. It does not mint identity. Everything that follows is a reason why that line exists and an accounting of what it costs to cross it.&lt;/p&gt;

&lt;p&gt;This article is a critical review of my own earlier piece, &lt;a href="https://dev.to/astaykov/the-industry-does-not-have-an-agent-identity-propagation-standard-yet-3fmf"&gt;The industry does not have an agent identity propagation standard yet&lt;/a&gt;. In that article I drew a cross-provider agent flow, and I drew it clean, because I wanted the propagation problem to be legible. The simplification did its job. It also hid the thing I now think matters most: in a real enterprise topology, the gateway quietly becomes an extra authorization server, and nobody counts it.&lt;/p&gt;

&lt;p&gt;Let me be honest about what this is. It is not a migration plan. It is a view, and an attempt to raise awareness of a risk that gets dismissed in design reviews with a shrug and the phrase "that is just how our platform works." I know these are brownfield estates. I know a re-platform does not land in a quarter, or in a year. I also know that most of the decisions that produced this shape were rational at the time they were made, taken by competent people under real constraints. None of that changes what the resulting architecture is. It is a liability, it deserves to be named as one, and naming it is not where the work stops.&lt;/p&gt;

&lt;p&gt;So here is the recommendation the rest of this article argues for, stated before the argument. &lt;strong&gt;Start consolidating your identity providers now, and keep driving the count down until the gateway is not one of them.&lt;/strong&gt; That is not a personal preference. Microsoft's &lt;a href="https://learn.microsoft.com/security/zero-trust/cisa-zero-trust-maturity-model-identity" rel="noopener noreferrer"&gt;CISA Zero Trust Maturity Model guidance for the identity pillar&lt;/a&gt; recommends consolidation of identity providers, identity stores, and identity management systems as the way to establish an identity foundation at all, and the &lt;a href="https://learn.microsoft.com/entra/standards/memo-22-09-enterprise-wide-identity-management-system" rel="noopener noreferrer"&gt;enterprise-wide identity management guidance for M-22-09&lt;/a&gt; puts the target plainly: as few managed identity systems as possible, with a consolidation plan behind them. Slow is acceptable. Not started is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  The words I use are OAuth words
&lt;/h2&gt;

&lt;p&gt;Before the argument, the vocabulary. I am not inventing terms here, and I want the disagreement to be about architecture rather than about definitions.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6749#section-1.1" rel="noopener noreferrer"&gt;RFC 6749 section 1.1&lt;/a&gt; defines four roles, and every one of them shows up in this article. The &lt;em&gt;resource owner&lt;/em&gt; is the entity capable of granting access to a protected resource, which in this article is Bob. The &lt;em&gt;resource server&lt;/em&gt; hosts the protected resource and accepts access tokens. The &lt;em&gt;client&lt;/em&gt; is the application making requests on the resource owner's behalf. The &lt;em&gt;authorization server&lt;/em&gt; issues tokens after authenticating the resource owner and obtaining authorization. &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; sits on top of that as the identity layer, which is how a client learns who Bob actually is rather than merely that some authorization exists.&lt;/p&gt;

&lt;p&gt;Four claims carry the whole argument.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;sub&lt;/code&gt; is the subject, the entity the token is about. &lt;a href="https://www.rfc-editor.org/rfc/rfc7519#section-4.1.2" rel="noopener noreferrer"&gt;RFC 7519 section 4.1.2&lt;/a&gt; defines it as the principal that is the subject of the JWT, and states plainly that the value is only guaranteed to be unique within the issuer's scope. Hold on to that sentence. It becomes the crux of this article.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;client_id&lt;/code&gt; identifies the client the token was issued to. &lt;a href="https://www.rfc-editor.org/rfc/rfc8693#section-4.3" rel="noopener noreferrer"&gt;RFC 8693 section 4.3&lt;/a&gt; defines it as the client identifier of the party to whom the token was issued, and &lt;a href="https://www.rfc-editor.org/rfc/rfc9068#section-2.2" rel="noopener noreferrer"&gt;RFC 9068 section 2.2&lt;/a&gt; makes it a required claim in a standards-compliant JWT access token. Microsoft Entra spells the same idea as &lt;code&gt;appid&lt;/code&gt; in v1.0 tokens and &lt;code&gt;azp&lt;/code&gt; in v2.0 tokens, which the &lt;a href="https://learn.microsoft.com/entra/identity-platform/access-token-claims-reference" rel="noopener noreferrer"&gt;access token claims reference&lt;/a&gt; documents alongside the rest of the claim set.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;act&lt;/code&gt; is the actor. &lt;a href="https://www.rfc-editor.org/rfc/rfc8693#section-4.1" rel="noopener noreferrer"&gt;RFC 8693 section 4.1&lt;/a&gt; defines it as the party acting on behalf of the subject, and it nests, so a chain of delegation can be expressed as a chain of &lt;code&gt;act&lt;/code&gt; claims.&lt;/p&gt;

&lt;p&gt;The rule that follows from those definitions is small, and it is the one people skip. &lt;strong&gt;&lt;code&gt;sub&lt;/code&gt; answers who authorized this, and the client and actor claims answer who is carrying that authorization.&lt;/strong&gt; Those are two different questions with two different answers, and an architecture that collapses them into one has stopped being able to answer either.&lt;/p&gt;

&lt;h2&gt;
  
  
  The gateway's job is governance, not authorization
&lt;/h2&gt;

&lt;p&gt;Start with the version of the world where everything is tidy.&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%2Flffvi8it42k8yxc13uie.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%2Flffvi8it42k8yxc13uie.png" alt="Simplified architecture with one API gateway and one identity provider, where the gateway routes traffic and every resource is registered with the same authorization server" width="799" height="406"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The estate is deliberately small. There is an AI agent. There are two remote MCP servers, A and B. There is a REST API that the agent calls directly through a local tool. There is a single-page application that Bob uses. There is Bob. Behind the MCP servers there are the systems they actually talk to, another REST API and a database. I have left the LLM out of the picture entirely, because the model is not an identity, and the REST API stands in well enough for "a thing the agent calls that has its own access control."&lt;/p&gt;

&lt;p&gt;One assumption holds this picture together, and I want it stated rather than implied. In this version, &lt;strong&gt;the gateway is not an authorization server.&lt;/strong&gt; Every resource in the estate is registered with the same identity provider. The gateway sits in the traffic path and does gateway things.&lt;/p&gt;

&lt;p&gt;What are gateway things? Discovery, so consumers can find an API without asking a human. Governance, so the platform team can see what is being called and by whom. Quotas and rate limiting, so one badly written client does not take down a shared dependency. Decoupling, so a backend can move without breaking every caller. Routing, transformation, and versioning. The &lt;a href="https://learn.microsoft.com/azure/api-management/authentication-authorization-overview" rel="noopener noreferrer"&gt;Azure API Management authentication and authorization overview&lt;/a&gt; describes exactly this shape of responsibility, and it is a good shape.&lt;/p&gt;

&lt;p&gt;Pre-authorization at the edge belongs in that list too. A gateway that checks whether an inbound token is well formed, unexpired, signed by a trusted issuer, and carrying the audience it should be carrying is doing valuable work. The &lt;a href="https://learn.microsoft.com/azure/api-management/validate-jwt-policy" rel="noopener noreferrer"&gt;validate-jwt policy&lt;/a&gt; is precisely the right shape of control, a cheap early sanity check that rejects obvious garbage before it reaches a backend. I have no argument with any of that.&lt;/p&gt;

&lt;p&gt;Here is where I draw the line. It is not the gateway's job to decide whether the SPA may invoke the agent. It is not the gateway's job to decide whether the agent may invoke MCP Server A rather than MCP Server B. Those are authorization decisions about business capability, and they belong to an authorization server that owns the identities and the entitlements involved. &lt;strong&gt;A gateway that pre-authorizes is enforcing a decision. A gateway that authorizes is making one, and those are not the same architectural role.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;A gateway that issues its own tokens has gone past both. It is not enforcing a decision and it is not making one. It is manufacturing the subject that every downstream decision will be made about. That is the identity provider's job, it is the only job on this page the gateway should not have, and no amount of policy configuration turns it into a gateway concern.&lt;/p&gt;

&lt;h2&gt;
  
  
  A plausible execution path
&lt;/h2&gt;

&lt;p&gt;Now run a request through it, and count the hops honestly.&lt;/p&gt;

&lt;p&gt;Bob signs in to the SPA (1). During that sign-in he also authorizes the SPA to invoke the AI agent on his behalf. He is the resource owner, the SPA is the client, and the agent is a resource he is delegating access to.&lt;/p&gt;

&lt;p&gt;Bob types a prompt. The SPA invokes the agent (2), carrying Bob's delegated authorization.&lt;/p&gt;

&lt;p&gt;The agent validates the request and builds a plan. The plan has three branches. It calls a local tool that hits a REST API (3). It calls MCP Server A (4), and MCP Server A must in turn call a downstream REST API (5) to do its work. It calls MCP Server B (6), and MCP Server B must run a query against a database (7).&lt;/p&gt;

&lt;p&gt;Because the agent is acting on Bob's behalf and not on its own account, Bob's identity has to survive every one of those hops. That is not a nice-to-have. If the downstream REST API at step 5 cannot tell that Bob is the reason it is being called, it cannot apply Bob's entitlements, and the agent has just become a confused deputy with a wider reach than any human user. So Bob must hold entitlements in the agent, in the REST API at step 3, in MCP Server A, in the REST API at step 5, in MCP Server B, and in the database at step 7.&lt;/p&gt;

&lt;p&gt;The MCP hops are not a matter of preference. The &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/authorization" rel="noopener noreferrer"&gt;MCP authorization specification&lt;/a&gt; treats MCP servers as OAuth 2.1 resource servers and states that a server MUST NOT pass through the token it received to an upstream service. Each hop is a fresh authorization decision by design, and the spec is right to require it. Token pass-through destroys audience binding and turns every server in the chain into an amplifier for whatever token happens to arrive.&lt;/p&gt;

&lt;p&gt;I want to be clear that this is not a contrived example built to make a point. It is the ordinary shape of agentic work. An agent that cannot call more than one tool, or whose tools never call anything themselves, is a demo. Anything doing real work in an enterprise looks like this or worse, and "worse" usually means more hops, more providers, and more places where the chain is only as strong as the weakest assertion in it.&lt;/p&gt;

&lt;h2&gt;
  
  
  In the ideal world, one authorization server sees all seven token requests
&lt;/h2&gt;

&lt;p&gt;Suppose the estate is greenfield and someone with authority made the obvious call: everything registers with one authorization server. Bob exists there as a user. The SPA is registered there as a client. The agent has its own identity there, distinct from Bob's. The REST APIs, both MCP servers, and the database all register there as resource servers with their own scopes.&lt;/p&gt;

&lt;p&gt;Seven token requests, and every one of them is an entitlement question with an answer.&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;The SPA authenticates Bob. Bob needs an entitlement to use the SPA at all.&lt;/li&gt;
&lt;li&gt;The SPA obtains delegated authorization to invoke the agent. Bob needs an entitlement to the agent, and the SPA needs to be authorized to ask for it on his behalf.&lt;/li&gt;
&lt;li&gt;The agent obtains a token for the REST API. Bob needs an entitlement in that API, and the agent needs to be permitted to request it.&lt;/li&gt;
&lt;li&gt;The agent obtains a delegated token for MCP Server A.&lt;/li&gt;
&lt;li&gt;MCP Server A obtains a token for the downstream REST API on Bob's behalf. Both MCP Server A and Bob need entitlements there, and they are separate checks.&lt;/li&gt;
&lt;li&gt;The agent obtains a delegated token for MCP Server B.&lt;/li&gt;
&lt;li&gt;MCP Server B obtains a token for the database on Bob's behalf, with the same double check.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This pattern has a name, and the standards already carry language for it. &lt;a href="https://www.rfc-editor.org/rfc/rfc8693#section-1.1" rel="noopener noreferrer"&gt;RFC 8693 section 1.1&lt;/a&gt; distinguishes delegation, where the acting party is given permission to act on the subject's behalf and both parties stay visible in the token, from impersonation, where the acting party disappears and the resource server cannot tell the difference. Microsoft's &lt;a href="https://learn.microsoft.com/entra/identity-platform/v2-oauth2-on-behalf-of-flow" rel="noopener noreferrer"&gt;on-behalf-of flow&lt;/a&gt; is the same idea with a vendor implementation attached.&lt;/p&gt;

&lt;p&gt;Now count how many times Bob appears as &lt;code&gt;sub&lt;/code&gt; in that chain. Seven times, issued by one issuer, with one meaning. There is one policy engine, one place where entitlements live, one enforcement surface, and therefore exactly one place where the entire chain can be denied. Revoke Bob at the authorization server and every hop downstream loses its footing on the next token request. Investigate an incident and there is one issuance log to correlate against.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;One authorization server, all resources registered with it, one policy engine.&lt;/strong&gt; Almost nobody reading this is standing there today, and I am not pretending otherwise. It remains the direction every estate should be actively moving in, and the distance from it is a number you should be able to say out loud: how many authorization servers issue tokens your systems will accept, and which of them has an owner and a retirement date. Microsoft's &lt;a href="https://learn.microsoft.com/entra/architecture/migration-best-practices" rel="noopener noreferrer"&gt;guidance on migrating apps and authentication to Microsoft Entra ID&lt;/a&gt; exists because that consolidation is a program of work rather than a configuration change. Programs of work have to be started before they can finish.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real world runs four authorization servers, not three
&lt;/h2&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%2Fi5fv11zomwrpm6s4z7tx.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%2Fi5fv11zomwrpm6s4z7tx.png" alt="Real world architecture where the SPA authenticates with IDP 0, the API gateway issues its own tokens from its own security token service, and downstream services trust IDP 1 and IDP 2" width="799" height="428"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The first reality is that you do not have one identity provider. You have several, with users in some of them, resources in others, and a partial overlap that nobody has fully mapped. And I do not mean the tidy split people reach for first, the on-premises directory alongside the cloud directory with a sync between them. That one at least has a documented relationship and an owner. I mean the genuine fragmentation: an acquired business unit with its own tenant, a SaaS platform with its own embedded authorization server, a legacy estate fronted by a federation product that predates everyone currently on the team, and a partner integration that authenticates against something nobody has permission to change.&lt;/p&gt;

&lt;p&gt;Every one of those has a story attached, and most of the stories are true. None of them makes the shape permanent. The number of identity providers in an estate is a design choice that got made incrementally, one acquisition and one integration at a time, and it comes back down the same way. Treat that number as a metric with an owner, not as weather.&lt;/p&gt;

&lt;p&gt;The second reality is what teams did about it. To hide that fragmentation from application developers, they made the gateway an authorization server. Not as an act of vandalism, and not against the documentation either. Fronting a backend with the gateway as the token audience is a supported, documented pattern, and the &lt;a href="https://learn.microsoft.com/azure/api-management/authentication-authorization-overview" rel="noopener noreferrer"&gt;APIM authentication and authorization overview&lt;/a&gt; covers scenarios in exactly that family. It works. It ships. It removes a class of problem from every application team at once, which is why it wins the design review.&lt;/p&gt;

&lt;p&gt;Walk the same flow through that topology.&lt;/p&gt;

&lt;p&gt;Step 1. Bob signs in to the SPA against IDP 0. So far, unchanged.&lt;/p&gt;

&lt;p&gt;Step 2. The agent is not directly reachable. It sits behind the gateway. So the resource server registered in IDP 0 is not the agent, it is the gateway. The SPA requests a token to invoke "something in the gateway," and IDP 0 has no opinion about which something, because the gateway is a single registered audience covering many backends.&lt;/p&gt;

&lt;p&gt;Step 2.1. This is the step that does not appear in anybody's architecture diagram, and it is the reason I am writing this. The gateway validates the incoming token from IDP 0, runs its own policy, and then issues its own token from its own security token service. From here inward, the estate no longer runs on IDP 0 tokens. It runs on gateway tokens.&lt;/p&gt;

&lt;p&gt;Step 3 is skipped here on purpose. I am keeping the numbering aligned with the ideal-world walkthrough so the two can be compared line by line, and the direct REST API call adds nothing that the MCP branches do not already show.&lt;/p&gt;

&lt;p&gt;Step 4. The agent is now free of external authorization servers, which was the entire selling point. It validates a gateway STS token and calls MCP Server A through the gateway. Clean, simple, and completely dependent on the gateway being right.&lt;/p&gt;

&lt;p&gt;Step 5. MCP Server A has to call a REST API that does not trust IDP 0 or the gateway. It trusts IDP 1. So MCP Server A registers as a client in IDP 1 and requests a token there. And that request has to assert three things at once: that Bob exists in IDP 1, that this Bob is the same Bob who signed in to IDP 0 at step 1, and that he holds entitlements in that REST API. Three assertions, and only the third is a normal authorization check.&lt;/p&gt;

&lt;p&gt;Steps 6 and 7. The same again, with MCP Server B, IDP 2, and the database behind it. Another registration, another mapping, another set of three assertions.&lt;/p&gt;

&lt;p&gt;Count the authorization servers in that picture. IDP 0, IDP 1, IDP 2, and the gateway's own STS. &lt;strong&gt;Four.&lt;/strong&gt; Ask an architect how many identity providers are in the flow and you will usually be told three, because the fourth one is drawn as a rectangle with a networking icon on it.&lt;/p&gt;

&lt;h2&gt;
  
  
  There are two places inside the gateway where Bob changes identity
&lt;/h2&gt;

&lt;p&gt;This is the part I want people to sit with.&lt;/p&gt;

&lt;p&gt;At step 2.1, Bob's authorization from IDP 0 becomes "Bob's" authorization from the gateway STS. At step 5, that gateway assertion becomes the basis for asserting Bob in IDP 1. Two translations, neither of them visible in the request path, neither of them producing an artifact that an auditor would recognize as an identity event.&lt;br&gt;
&lt;/p&gt;

&lt;pre data-lang="mermaid"&gt;&lt;code&gt;flowchart LR
    Bob["Bob&amp;lt;br/&amp;gt;IDP 0"]
    SPA["SPA"]
    GW["API&amp;lt;br/&amp;gt;gateway"]
    Agent["Agent"]
    MCPA["MCP&amp;lt;br/&amp;gt;Server A"]
    API["REST API&amp;lt;br/&amp;gt;trusts IDP 1"]

    Bob --&amp;gt;|"1. sign-in&amp;lt;br/&amp;gt;delegates&amp;lt;br/&amp;gt;to SPA"| SPA
    SPA --&amp;gt;|"2. token&amp;lt;br/&amp;gt;iss: IDP 0&amp;lt;br/&amp;gt;aud: gateway"| GW
    GW --&amp;gt;|"TRANSLATION 1&amp;lt;br/&amp;gt;step 2.1&amp;lt;br/&amp;gt;gateway STS&amp;lt;br/&amp;gt;mints Bob"| Agent
    Agent --&amp;gt;|"4. routed&amp;lt;br/&amp;gt;via gateway"| MCPA
    MCPA --&amp;gt;|"TRANSLATION 2&amp;lt;br/&amp;gt;step 5&amp;lt;br/&amp;gt;asserts Bob&amp;lt;br/&amp;gt;into IDP 1"| API

    linkStyle 2 stroke:#d62728,stroke-width:3px
    linkStyle 4 stroke:#d62728,stroke-width:3px&lt;/code&gt;&lt;/pre&gt;



&lt;p&gt;Remember what &lt;a href="https://www.rfc-editor.org/rfc/rfc7519#section-4.1.2" rel="noopener noreferrer"&gt;RFC 7519 section 4.1.2&lt;/a&gt; actually says about &lt;code&gt;sub&lt;/code&gt;. The value is unique within the issuer's scope. Not globally. Not across your estate. Within the issuer. So when the gateway STS emits a token whose &lt;code&gt;sub&lt;/code&gt; reads like Bob, that value means whatever the gateway says it means, and when MCP Server A turns that into an IDP 1 assertion, the identity has been through two mappings that no standard is validating for you.&lt;/p&gt;

&lt;p&gt;The consequence is one sentence long. &lt;strong&gt;Whoever controls the gateway decides who Bob is.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Two questions follow, and in every estate where I have asked them, neither has had a satisfying answer. First, how do you trace that the Bob who signed in at step 1 is the Bob whose entitlements were used at step 5? The correlation exists, if it exists at all, in gateway policy configuration and gateway logs, which are owned by a different team than the one that owns the identity logs. Second, if Bob at step 1 is compromised, what locks down Bob at step 5? Disabling the account in IDP 0 stops new sign-ins. It does not tell the gateway STS to stop minting, and it does not tell IDP 1 anything at all, because IDP 1 was never told that its Bob and IDP 0's Bob were related.&lt;/p&gt;

&lt;p&gt;Compare that with what the standards are actually asking for. &lt;a href="https://www.rfc-editor.org/rfc/rfc8707" rel="noopener noreferrer"&gt;RFC 8707 resource indicators&lt;/a&gt; exists so a client can name the resource it wants a token for, and so the authorization server can bind the token to that audience. The &lt;a href="https://modelcontextprotocol.io/specification/2025-06-18/basic/security_best_practices" rel="noopener noreferrer"&gt;MCP security best practices&lt;/a&gt; require a server to reject any token that was not issued for it, and name the confused deputy problem explicitly. Both controls point the same direction: narrow the audience, keep the issuer meaningful, refuse tokens minted for somebody else. A gateway that re-mints identity at the edge is doing the opposite of audience binding. It is deliberately widening the audience so that everything behind it can stop caring.&lt;/p&gt;

&lt;p&gt;So here is the position, and I hold it without qualification. In trying to make things simpler behind the gateway, the industry made them worse, because it pushed the gateway into acting as an identity provider without ever admitting that is what happened. The simplicity is real. It was purchased with a trust anchor, and the invoice has not been read. A gateway that mints identity is not a pragmatic shortcut with a documentation page behind it. It is an unowned authorization server sitting in the middle of your estate, and it should not be there.&lt;/p&gt;

&lt;h2&gt;
  
  
  Four policy engines is a liability, and it should be called one
&lt;/h2&gt;

&lt;p&gt;I will concede the same ground twice, because it is fair ground. These are brownfield systems. They carry years of integrations, contracts, and behavior that nobody has fully documented. The gateway STS does not come out next sprint, and I am not asking anyone to pretend otherwise.&lt;/p&gt;

&lt;p&gt;The concession explains why this is hard. It does not excuse leaving it alone. Two things have to happen, and they run in parallel rather than in sequence. The first is naming the liability, because the first control you apply to a liability is writing its real name into a register that somebody reviews. The second is reducing the number of authorization servers in the estate on a schedule that has an owner, starting with the one that was never classified as an authorization server in the first place.&lt;/p&gt;

&lt;p&gt;Four authorization servers means four policy engines. Four places where entitlements are managed, with four different models for expressing them. Four places where human and non-human identities are provisioned and deprovisioned, with four different joiner, mover, leaver processes, one of which is a configuration file. Four places where a broad and fragile trust relationship gets created, and only three of them will show up in an identity governance review.&lt;/p&gt;

&lt;p&gt;The ownership asymmetry is the part that should worry you most. The gateway belongs to the application platform team. It does not belong to security, and it very rarely belongs to IAM. So the controls that IDP 0, IDP 1, and IDP 2 are subject to, privileged access review, signing key custody and rotation, conditional access on administrative operations, change approval on trust configuration, alerting on issuance policy changes, are simply not applied to the fourth authorization server. Not because anyone decided to exempt it. Because nobody classified it as one.&lt;/p&gt;

&lt;p&gt;Now look at it the way an adversary does. IDP 1 gets you the users and resources in IDP 1. The gateway STS gets you an assertion that IDP 1 will accept, an assertion that IDP 2 will accept, and an assertion that every service behind the gateway will accept without further question. &lt;strong&gt;To an attacker, that gateway is worth more than IDP 1.&lt;/strong&gt; It is also patched on an application team's schedule, monitored with application telemetry, and administered by people whose access was never reviewed as identity provider access, because on the asset inventory it is middleware.&lt;/p&gt;

&lt;h2&gt;
  
  
  RFC 8693 does not fix a broken trust anchor
&lt;/h2&gt;

&lt;p&gt;Whenever I make this argument, somebody reaches for token exchange, so let me address it directly rather than leave it sitting as an unstated rebuttal.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8693" rel="noopener noreferrer"&gt;RFC 8693&lt;/a&gt; is a good specification and I am not arguing against using it. It gives you a real protocol for a security token service: a &lt;code&gt;subject_token&lt;/code&gt; describing the party on whose behalf the request is made, an optional &lt;code&gt;actor_token&lt;/code&gt; describing the party doing the acting, and a token type system that lets you convert between formats deliberately instead of by convention. It gives you the &lt;code&gt;act&lt;/code&gt; claim as a nested, inspectable delegation history, so a resource server can see the whole chain rather than a flattened identity. It even gives you &lt;a href="https://www.rfc-editor.org/rfc/rfc8693#section-4.4" rel="noopener noreferrer"&gt;&lt;code&gt;may_act&lt;/code&gt;&lt;/a&gt;, a claim stating which party is authorized to become the actor for a subject, which is genuinely the right primitive for constraining delegation.&lt;/p&gt;

&lt;p&gt;Here is what it does not give you: a reason to believe the mapping. RFC 8693 supplies mechanics for exchanging one token for another. It explicitly places the trust model and the policy governing exchanges outside its scope, and it imposes no requirements on how a deployment decides that this subject in this issuer corresponds to that subject in that issuer. That decision is yours. In the architecture above, the answer to "why should IDP 1 believe this is Bob" is that somebody configured a mapping in the gateway's STS, carefully, once, some time ago, and it has been correct ever since as far as anyone knows.&lt;/p&gt;

&lt;p&gt;The specification is candid about where that leads. It warns that when an authorization server does not authenticate the client in a token exchange, a compromised or leaked token can be leveraged through the STS into other tokens, and it tells you to require client authentication and to constrain who may exchange what. Apply that warning to a gateway STS that fronts an entire estate and the exposure takes an obvious shape. Control the gateway STS, or control anything that can persuade it to mint, and you can obtain a token for any user in any provider that trusts it. Not through a vulnerability. Through the feature.&lt;/p&gt;

&lt;p&gt;So the closing rule is the one I would defend in any design review. &lt;strong&gt;Token exchange is a mechanism for propagating identity. It is not a mechanism for establishing who is allowed to assert it.&lt;/strong&gt; The propagation problem is largely solved, and the standards are in decent shape. The assertion problem is the one your architecture answered by accident.&lt;/p&gt;

&lt;p&gt;Which brings me back to where I started, and I will state it once more without softening it. &lt;strong&gt;It is not the API gateway's job to be an identity provider.&lt;/strong&gt; Discovery, governance, quotas, decoupling, routing, versioning, and pre-authorization at the edge are the gateway's work, and a good gateway is genuinely excellent at all of it. Minting the subject that the rest of the estate authorizes against was never on that list and should not have been added to it.&lt;/p&gt;

&lt;p&gt;Two actions follow, and they are not alternatives to each other.&lt;/p&gt;

&lt;p&gt;Start the first one this quarter. If the gateway is asserting identity today, govern it as an identity provider, because the only other option available right now is an ungoverned one. Put it in the identity asset inventory. Review its administrators as privileged identity administrators. Rotate and custody its signing keys the way you custody an identity provider's. Put change approval on its trust configuration and alerting on its issuance policy. Feed its issuance logs into the same correlation surface as every other issuer, so a chain from step 1 to step 5 can actually be reconstructed.&lt;/p&gt;

&lt;p&gt;Then fund the second one. Count the authorization servers your estate trusts, put a name and a date against each one you intend to retire, and drive that number down until the gateway is out of the identity business entirely and back to being the very good gateway it already is. That work is slow, it is unglamorous, and it will outlast several reorganizations. Start it anyway, because the count does not fall on its own.&lt;/p&gt;

&lt;p&gt;And hold the line on new build while you do it. Nothing greenfield gets to add another authorization server, and nothing new gets fronted by a gateway that mints its own tokens. If you will not govern the gateway as an identity provider, do not let it assert identity.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://dev.to/astaykov/the-industry-does-not-have-an-agent-identity-propagation-standard-yet-3fmf"&gt;The industry does not have an agent identity propagation standard yet&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6749#section-1.1" rel="noopener noreferrer"&gt;RFC 6749 section 1.1, OAuth 2.0 roles&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://www.rfc-editor.org/rfc/rfc7519#section-4.1.2" rel="noopener noreferrer"&gt;RFC 7519 section 4.1.2, the sub claim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8693" rel="noopener noreferrer"&gt;RFC 8693, OAuth 2.0 Token Exchange&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8693#section-1.1" rel="noopener noreferrer"&gt;RFC 8693 section 1.1, delegation and impersonation&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8693#section-4.1" rel="noopener noreferrer"&gt;RFC 8693 section 4.1, the act claim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8693#section-4.3" rel="noopener noreferrer"&gt;RFC 8693 section 4.3, the client_id claim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8693#section-4.4" rel="noopener noreferrer"&gt;RFC 8693 section 4.4, the may_act claim&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9068#section-2.2" rel="noopener noreferrer"&gt;RFC 9068 section 2.2, required claims in JWT access tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc8707" rel="noopener noreferrer"&gt;RFC 8707, Resource Indicators for OAuth 2.0&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/access-token-claims-reference" rel="noopener noreferrer"&gt;Microsoft Entra access token claims reference&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 on-behalf-of flow&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/security/zero-trust/cisa-zero-trust-maturity-model-identity" rel="noopener noreferrer"&gt;CISA Zero Trust Maturity Model for the identity pillar&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/standards/memo-22-09-enterprise-wide-identity-management-system" rel="noopener noreferrer"&gt;Memo 22-09 enterprise-wide identity management system&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/architecture/migration-best-practices" rel="noopener noreferrer"&gt;Migrate application authentication to Microsoft Entra ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/api-management/authentication-authorization-overview" rel="noopener noreferrer"&gt;Azure API Management authentication and authorization overview&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>apigateway</category>
      <category>oauth</category>
      <category>enterprisearchitecture</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>AI agents need an authorization domain beyond OAuth</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Thu, 03 Sep 2026 10:03:00 +0000</pubDate>
      <link>https://dev.to/astaykov/ai-agents-need-an-authorization-domain-beyond-oauth-5167</link>
      <guid>https://dev.to/astaykov/ai-agents-need-an-authorization-domain-beyond-oauth-5167</guid>
      <description>&lt;p&gt;OAuth 2.0 is not broken because an AI agent uses it. It remains the right protocol family for issuing and presenting access tokens, expressing delegation, and binding a client to an authorization transaction. &lt;a href="https://www.rfc-editor.org/rfc/rfc9700.html" rel="noopener noreferrer"&gt;RFC 9700&lt;/a&gt; still recommends restricted tokens, audience checks, and sender constraints where applicable. That is not the problem.&lt;/p&gt;

&lt;p&gt;The problem is the quiet assumption that a valid token and a permitted API operation fully answer whether an agent should perform a particular consequential action now. An agent can turn one user request into a chain of tool calls, select values that the user did not enumerate, and reach a side effect that no scope string can describe by itself. &lt;strong&gt;A valid grant is not a standing instruction to proceed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The industry needs a distinct &lt;em&gt;agent-authorization domain&lt;/em&gt; between token issuance and the business operation. It combines the authorization server's proof of grant and delegation, a fine-grained policy engine's model of principal-action-resource semantics, and a context- and risk-driven decision engine. The resource still enforces the result. No one component should impersonate all the others.&lt;/p&gt;

&lt;p&gt;This is also where a future interoperability standard could matter. It would give security teams, agent developers, and application operators one plane for describing an action, carrying an evaluated disposition, and proving what the PEP enforced. Without a shared direction, each vendor will define agent authorization semantics differently. The result is fragmented controls, inconsistent decisions across tool boundaries, and complexity that does not improve the transaction. &lt;strong&gt;This is a proposed future direction, not an existing standard.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  OAuth proves a grant, not the meaning of an agent transaction
&lt;/h2&gt;

&lt;p&gt;OAuth defines the roles that matter here: an authorization server issues access tokens after a grant, and a resource server accepts a token to protect a resource. &lt;a href="https://www.rfc-editor.org/rfc/rfc6749.html" rel="noopener noreferrer"&gt;RFC 6749&lt;/a&gt; deliberately leaves the resource owner, client, authorization server, and resource server with separate responsibilities. That separation remains valuable when the client happens to be an agent.&lt;/p&gt;

&lt;p&gt;A protected API must validate a token intended for it before using it as proof of authorization. &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens" rel="noopener noreferrer"&gt;Microsoft's access-token guidance&lt;/a&gt; is equally clear that the API owns token validation and that clients should treat access tokens as opaque. OAuth security guidance also says tokens should be restricted to the minimum privileges, resources, and actions required, with resource servers enforcing those restrictions for every request. &lt;a href="https://www.rfc-editor.org/rfc/rfc9700.html" rel="noopener noreferrer"&gt;RFC 9700&lt;/a&gt; defines that as a core best practice.&lt;/p&gt;

&lt;p&gt;That is standardized token and delegation mechanics. It does not supply a universal semantic model for questions such as: Is this payroll export for an approved case? Is the new payee related to the current supplier? Has this agent already attempted the same action through another tool? Should a proposed bulk deletion be staged rather than committed? Those are business- and transaction-specific questions. They require data the authorization server neither owns nor should be expected to infer.&lt;/p&gt;

&lt;p&gt;OAuth Rich Authorization Requests is a useful bridge, not the destination. &lt;code&gt;authorization_details&lt;/code&gt; lets a client request structured authorization data that an authorization server and protected resource can interpret, including resource, action, and transaction attributes. &lt;a href="https://www.rfc-editor.org/rfc/rfc9396.html" rel="noopener noreferrer"&gt;RFC 9396&lt;/a&gt; gives the protected API and authorization server that shared vocabulary. It does not turn a bearer or sender-constrained token into a live model of an agent's tool chain, business purpose, or current risk evidence. &lt;strong&gt;Use OAuth to establish the authorized envelope. Do not turn the token into a mutable risk dossier.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Fine-grained policy gives the transaction its business meaning
&lt;/h2&gt;

&lt;p&gt;An agent-authorization domain needs a policy decision point that understands the action as the business sees it, rather than only as an HTTP method against a route. Cedar is one example: its policies evaluate a principal, action, resource, entity attributes, and transient request context. &lt;a href="https://docs.cedarpolicy.com/" rel="noopener noreferrer"&gt;Cedar's language documentation&lt;/a&gt; describes this model and its separation of policy from application code. Similar fine-grained authorization systems can fill the same role.&lt;/p&gt;

&lt;p&gt;That distinction matters because &lt;code&gt;POST /vendors/88421&lt;/code&gt; is not a useful authorization verb. &lt;code&gt;vendor.bank-account.change&lt;/code&gt; is. It can have a resource owner, a value change, a financial consequence, a required approver, and a rule that applies to this action but not to a vendor-address correction. The policy layer answers whether the authenticated agent, acting within a valid delegated or application grant, may propose this precise action on this precise resource.&lt;/p&gt;

&lt;p&gt;The policy decision point must receive trustworthy attributes, not a bag of agent-generated labels. The resource owner supplies canonical resource attributes. The workflow supplies the operation and the proposed effect. The identity layer supplies validated principal and grant attributes. The agent can supply a declared purpose, but a policy should treat that as an assertion until an authoritative workflow record corroborates it. &lt;strong&gt;The agent may describe intent. The authorization domain decides which facts are authoritative.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;validated identity and grant     canonical business data
             |                            |
             v                            v
      authorization server --&amp;gt; policy decision point
                                      |
agent tool plan and request context ---+--&amp;gt; policy result: permitted or denied
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;a href="https://microsoft.github.io/agent-governance-toolkit/ARCHITECTURE/" rel="noopener noreferrer"&gt;Agent Governance Toolkit architecture&lt;/a&gt; is a concrete application-layer reference architecture for this enforcement shape. It evaluates agent actions against policy before execution and separates policy, identity and trust, runtime controls, tool-call interception, and audit components. That makes it useful implementation evidence for putting a governance gate before an agent action. It is not a platform-neutral authorization protocol, and its component specifications and conformance tests describe the toolkit's own architecture rather than a shared interoperability contract. &lt;strong&gt;Reference architectures can prove an enforcement pattern. They do not, by themselves, standardize the decision exchange between systems.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;This is already more precise than broad scopes. It is still not the complete decision for an autonomous actor, because permission semantics do not determine whether the current circumstances make execution acceptable.&lt;/p&gt;

&lt;h2&gt;
  
  
  Static policy needs bounded intelligent judgment for changing context
&lt;/h2&gt;

&lt;p&gt;Static policies remain necessary deterministic guardrails. They define non-negotiable constraints: which principals may act, which business actions are categorically prohibited, which resources require an approval, and which conditions must always deny or stage an effect. Cedar, for example, evaluates explicit principal, action, resource, attribute, and request-context inputs against authored policies. &lt;a href="https://docs.cedarpolicy.com/" rel="noopener noreferrer"&gt;Its language documentation&lt;/a&gt; describes that decision model. &lt;strong&gt;An adaptive system may not weaken a hard policy constraint.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Static policies alone cannot safely resolve every agent-authorization question. An autonomous actor encounters changing, incomplete, and cross-domain context that a policy author cannot enumerate exhaustively in advance. A bounded intelligent decision system can synthesize that evidence and propose, or make, a transaction decision only within an explicit authority envelope. The resource PEP must enforce that envelope, log the evidence and outcome, and reject decisions that are expired, unbound to the proposed effect, or outside its authority. The system must be tested and evaluated for its intended decisions, with human escalation for consequential or ambiguous cases. &lt;a href="https://www.nist.gov/itl/ai-risk-management-framework" rel="noopener noreferrer"&gt;NIST's AI Risk Management Framework&lt;/a&gt; provides voluntary guidance for governing and evaluating AI systems, not an authorization protocol. &lt;strong&gt;Adaptive judgment can operate inside policy. It cannot replace policy ownership or resource enforcement.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The third component is therefore a context- and risk-driven decision engine. It evaluates signals that are time-bound, cumulative, or external to the grant: the sensitivity and volume of data, destination reputation, a sequence of failed attempts, an unusual operation rate, an open investigation, or a human approval that has expired. The engine returns a bounded disposition for this transaction. It does not mint access tokens, reinterpret OAuth grants, silently rewrite authorization policy, or become the policy owner for every resource.&lt;/p&gt;

&lt;p&gt;This boundary is particularly important for insider risk. Microsoft Purview Insider Risk Management correlates service and third-party indicators to identify potentially malicious or inadvertent activity, then routes evidence through alerts, triage, investigation, and action. &lt;a href="https://learn.microsoft.com/en-us/purview/insider-risk-management" rel="noopener noreferrer"&gt;Its documented workflow&lt;/a&gt; is an investigative and response system, not a generic inline API authorization server. The &lt;em&gt;Risky Agents&lt;/em&gt; template detects signals including risky prompts, sensitive responses, access to sensitive or priority SharePoint files, risky websites, external sharing, and activity above an agent baseline for supported agent platforms. &lt;a href="https://learn.microsoft.com/en-us/purview/insider-risk-management-policy-templates" rel="noopener noreferrer"&gt;The policy-template documentation&lt;/a&gt; defines that scope.&lt;/p&gt;

&lt;p&gt;Those signals are useful evidence. They are not enough, by themselves, to dictate every API outcome. A risk system may know an agent's activity is unusual; the resource owner knows whether this operation changes a bank account, publishes a document, or merely reads a record. &lt;strong&gt;Risk supplies a disposition. The business policy supplies the meaning.&lt;/strong&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Agent proposes a business action
             |
             +--&amp;gt; authorization server: is the grant and delegation valid?
             |
             +--&amp;gt; policy decision point: may this principal propose this action on this resource?
             |
             +--&amp;gt; risk context engine: should this transaction proceed, hold, or require review now?
             |
             +--&amp;gt; resource policy enforcement point: commit, deny, hold, or escalate
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The resource or application &lt;em&gt;policy enforcement point (PEP)&lt;/em&gt; owns the final side effect because it owns the target state. This placement avoids asking an authorization server to understand every business transaction and avoids asking an insider-risk program to become an unbounded authorization service.&lt;/p&gt;

&lt;h2&gt;
  
  
  A proposed decision contract must bind decisions to the proposed effect
&lt;/h2&gt;

&lt;p&gt;An agent-authorization decision cannot be &lt;code&gt;allow&lt;/code&gt; in the abstract. A proposed future &lt;em&gt;decision contract&lt;/em&gt; must bind to the proposed effect, the relevant version of policy, the evaluated context, and a short validity period. Otherwise, an agent can obtain a decision for a harmless request and reuse it for a different one.&lt;/p&gt;

&lt;p&gt;Consider a procurement agent that receives a valid delegated grant to update supplier records. It proposes changing supplier &lt;code&gt;88421&lt;/code&gt; from one bank account to another. The policy engine verifies that the agent is allowed to propose this kind of change for that supplier. The risk engine sees that the destination account is new, the amount of pending payments is material, and the agent has already attempted a similar change through a second workflow. The PEP stages the update rather than committing it.&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;"transaction_id"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"6bfbdbd2-0f82-4b44-a27a-e1d939bc0d38"&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;"agent:vendor-onboarding"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"action"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vendor.bank-account.change"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"resource"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vendor:88421"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"proposed_effect_hash"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"sha256:4d2c..."&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"policy"&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="nl"&gt;"decision"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"permit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"policy_version"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"vendor-change-v3"&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="nl"&gt;"risk"&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="nl"&gt;"disposition"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"hold"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"reason_codes"&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;"NEW_DESTINATION"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"DUPLICATE_ATTEMPT"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"expires_at"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"2026-09-01T14:20:00Z"&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="nl"&gt;"enforcement"&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="nl"&gt;"outcome"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"staged"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"required_approver"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"finance-controller"&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;The hash stands for a normalized representation of the proposed change, not a raw prompt. The PEP must recompute it before committing, reject an expired disposition, and record the policy and risk versions it relied on. This is ordinary transaction integrity applied to an agent decision, not a new claim about what OAuth tokens contain.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;allow&lt;/code&gt;, &lt;code&gt;deny&lt;/code&gt;, &lt;code&gt;hold&lt;/code&gt;, and &lt;code&gt;escalate&lt;/code&gt; are more useful than a single numerical score. &lt;code&gt;allow&lt;/code&gt; permits the PEP to perform the already-authorized operation. &lt;code&gt;deny&lt;/code&gt; prevents it. &lt;code&gt;hold&lt;/code&gt; preserves a resumable state without the external side effect. &lt;code&gt;escalate&lt;/code&gt; routes a specifically described proposed effect to a named approver or workflow. This vocabulary is an example of a proposed product contract, not a standard. Any such contract must be deterministic enough to test and audit. &lt;strong&gt;A risk score is evidence. A disposition is an enforceable decision.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Delegation does not erase the human authority boundary
&lt;/h2&gt;

&lt;p&gt;Delegated authority is paramount when an agent operates in a human account context or on behalf of a human. OAuth defines the resource owner as the entity capable of granting access and models the client as a separate role. &lt;a href="https://www.rfc-editor.org/rfc/rfc6749.html" rel="noopener noreferrer"&gt;RFC 6749&lt;/a&gt; preserves that distinction even when the client acts with a delegated grant. The agent may execute within that grant. It does not become the human principal, nor does an earlier authorization settle every later consequential choice.&lt;/p&gt;

&lt;p&gt;Long-running operations make the boundary visible. An agent can collect evidence, coordinate tools, and reach a decision point hours after the human's initiating instruction. A future architecture should define a &lt;em&gt;human-authority backchannel&lt;/em&gt; through which the agent can present the exact proposed effect, receive an approval, rejection, clarification, or cancellation, and bind that response to the transaction before the PEP commits. &lt;/p&gt;

&lt;p&gt;The backchannel must be independent of agent-generated prose. The PEP or workflow service should construct the request from canonical action, resource, effect, deadline, and consequence data, then record the human response with the transaction. A chat message that says "looks good" is not enough unless the system can bind it to one immutable proposed effect. &lt;strong&gt;Delegated execution is not delegated sovereignty. The human retains authority over the consequential commitment.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Start with transactions that have a reversible boundary
&lt;/h2&gt;

&lt;p&gt;The sharp edge is timing. An asynchronous investigation system cannot safely become an inline decision point unless its latency, availability, freshness, and failure behavior are explicitly engineered for that job. Purview's documented workflow moves from policy matches to alerts, reviewer triage, cases, and actions. &lt;a href="https://learn.microsoft.com/en-us/purview/insider-risk-management" rel="noopener noreferrer"&gt;Microsoft's workflow documentation&lt;/a&gt; supports a different operational purpose from a low-latency PEP call.&lt;/p&gt;

&lt;p&gt;Begin where the application has a natural pending state. A customer-export agent can assemble the candidate set, evaluate sensitivity labels and destination, then hold the delivery link until a data owner approves. A knowledge-management agent can stage an external publication or bulk deletion and submit the exact diff for review. A payments agent can save a proposed bank-account change while preventing release of pending payments until the required approval arrives. For long-running delegated work, this pending state is the practical location for a proposed human-authority backchannel.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;authorize grant -&amp;gt; evaluate business policy -&amp;gt; evaluate risk context
       -&amp;gt; stage proposed effect -&amp;gt; approve or reject -&amp;gt; commit once
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This design creates a real control point before the state change. Trying to reverse an external email, payment, or published document after an alert appears is incident response, not authorization. &lt;strong&gt;The PEP is the last trustworthy place to stop a consequential transaction.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  Record observed facts, not an invented provenance story
&lt;/h2&gt;

&lt;p&gt;Every agent transaction needs a durable record at the PEP. It should identify the authenticated agent principal, validated grant reference, initiating workflow or user context when observed, action, resource, proposed-effect hash, policy result, risk disposition, enforcement outcome, and correlation identifiers. That lets an investigation connect agent traces, authorization events, application telemetry, and a case without claiming that an access token proves who authored every intermediate model decision.&lt;/p&gt;

&lt;p&gt;The privacy boundary belongs in the design, not the postmortem. Purview describes privacy-by-design controls including default pseudonymization, role-based access control, and audit logs. &lt;a href="https://learn.microsoft.com/en-us/purview/insider-risk-management" rel="noopener noreferrer"&gt;Microsoft's overview&lt;/a&gt; documents those safeguards. An agent-authorization record should minimize sensitive context, separate access to raw content from access to decision metadata, and retain each according to a documented purpose and schedule. &lt;strong&gt;Observability is not permission to copy every prompt into every audit system.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;NIST's AI Risk Management Framework is voluntary guidance for incorporating trustworthiness considerations into the design, development, use, and evaluation of AI systems. &lt;a href="https://www.nist.gov/itl/ai-risk-management-framework" rel="noopener noreferrer"&gt;NIST AI RMF&lt;/a&gt; is useful here as a governance frame, but it does not substitute for a transaction protocol or a resource-specific enforcement decision. Risk governance and agent authorization must meet at the operation, where both can be made concrete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Do not make the authorization server carry the whole system
&lt;/h2&gt;

&lt;p&gt;It is tempting to solve the agent problem by packing richer claims into tokens or by making the authorization server query every risk source. That centralizes business semantics, changing risk data, and resource-specific side-effect knowledge in a component that cannot reliably own them. The result is either a fragile token schema or a shadow business-policy engine hidden behind token issuance.&lt;/p&gt;

&lt;p&gt;Keep the four responsibilities legible. The authorization server establishes a valid grant and delegation boundary. Static policy defines deterministic, non-negotiable constraints for the named principal, business action, and resource. The bounded context and risk engine synthesizes admissible evidence into a time-bound disposition inside its explicit authority envelope. The PEP binds those outcomes to the proposed effect, enforces the envelope, records the decision, and controls the commit.&lt;/p&gt;

&lt;p&gt;That is the proposed evolution. Current authorization standards and static policies remain necessary, and fine-grained authorization frameworks already solve a meaningful part of the semantic problem. The additional requirement is a bounded intelligent decision capability for context that is changing, incomplete, or spread across domains, with testing, logging, resource enforcement, and a human backchannel for consequential or ambiguous commitments. A future interoperability standard could define common decision and evidence exchanges while preserving resource-specific policy, the authority envelope, and the human authority boundary. This is an architectural argument, not a claim that a standard already defines it.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc6749.html" rel="noopener noreferrer"&gt;RFC 6749: The OAuth 2.0 Authorization Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9396.html" rel="noopener noreferrer"&gt;RFC 9396: OAuth 2.0 Rich Authorization Requests&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.rfc-editor.org/rfc/rfc9700.html" rel="noopener noreferrer"&gt;RFC 9700: Best Current Practice for OAuth 2.0 Security&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/access-tokens" rel="noopener noreferrer"&gt;Access tokens in the Microsoft identity platform&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/purview/insider-risk-management" rel="noopener noreferrer"&gt;Learn about Insider Risk Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/purview/insider-risk-management-policy-templates" rel="noopener noreferrer"&gt;Learn about Insider Risk Management policy templates&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.cedarpolicy.com/" rel="noopener noreferrer"&gt;Cedar Policy Language Reference Guide&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.nist.gov/itl/ai-risk-management-framework" rel="noopener noreferrer"&gt;NIST AI Risk Management Framework&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://microsoft.github.io/agent-governance-toolkit/ARCHITECTURE/" rel="noopener noreferrer"&gt;Agent Governance Toolkit architecture&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>oauth</category>
      <category>identitygovernance</category>
      <category>aiagents</category>
    </item>
    <item>
      <title>Authorization options for your AI agents with Entra Agent ID</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Thu, 27 Aug 2026 09:43:00 +0000</pubDate>
      <link>https://dev.to/astaykov/authorization-options-for-your-ai-agents-with-entra-agent-id-4p0k</link>
      <guid>https://dev.to/astaykov/authorization-options-for-your-ai-agents-with-entra-agent-id-4p0k</guid>
      <description>&lt;p&gt;Authorization in Entra has changed, and most of that change is not visible in the product UI. The boundaries have always been there; they are just no longer optional side-quests. When you architect an Entra Agent ID, you are not choosing &lt;em&gt;one&lt;/em&gt; authorization mechanism. You are composing five of them, each with its own trust surface. Understanding that composition is the difference between a loosely guarded identity and one that respects the principle of least privilege.&lt;/p&gt;

&lt;p&gt;Consent is authorization, not authentication. But consent is only the start. After you have granted the app permission to call Microsoft Graph, the hard work is to ensure it calls Graph with the minimum scope it actually needs, at the exact moment it needs it, and only on behalf of the principals you intended. That is what this article addresses.&lt;/p&gt;

&lt;h2&gt;
  
  
  User assignment as your outermost gate
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/manage-apps/assign-user-or-group-access-portal" rel="noopener noreferrer"&gt;User assignment&lt;/a&gt; is the first boundary you should establish. It is blunt, but it is not optional. When you require user or group assignment to an enterprise application, you are saying: "Only the principals I explicitly list in this Entra app object can even attempt to authenticate to this workload."&lt;/p&gt;

&lt;p&gt;For user-facing applications, this is straightforward. You assign Alice and Bob to the app, and only Alice and Bob can sign in.&lt;/p&gt;

&lt;p&gt;For an AI agent, user assignment works similarly. You do not assign the agent's identity to an app. Instead, you assign the &lt;em&gt;users&lt;/em&gt; who are allowed to delegate to or authenticate to the agent. In other words that assignment controls which users can invoke the agent or have the agent act on their behalf.&lt;/p&gt;

&lt;p&gt;Here is the critical part: user assignment gates access at two levels. First, it controls which users can authenticate to the agent itself. Second, and less obvious, it also gates downstream resource access. If a user is not explicitly assigned to a downstream API or resource, the agent cannot access that resource on behalf of that user—even if the agent's identity is assigned to that API. User assignment is not negotiable at any layer of the call chain.&lt;/p&gt;

&lt;p&gt;If you do not require assignment, Entra will still respect the OAuth 2.0 consent framework. However, it will allow any user in your tenant (or any user in a federated tenant, if you have configured cross-tenant access) to attempt authentication. The app sees the request, asks for consent, the user grants it (or the admin pre-consents), and the app gets a token. User assignment closes that loophole. It says: "No request reaches this workload unless the requester is already in my approved list—and no downstream resource request on behalf of that user will succeed if the user is not approved there either."&lt;/p&gt;

&lt;p&gt;For AI agents deployed at scale across teams or business units, user assignment is your guard rail. Without it, you are depending on the app and downstream APIs to reject requests from unknown principals. The better defense is to never admit an unapproved principal to the conversation in the first place, and to enforce that control consistently across all resources the agent might access.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important:&lt;/strong&gt; &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/howto-restrict-your-app-to-a-set-of-users" rel="noopener noreferrer"&gt;User assignment requirement&lt;/a&gt; works for both Agent Identity Blueprints and individual agent identities. However, there is currently no Entra Admin Center UX that exposes user assignment functionality — only the Microsoft Graph API. For practical user assignment at scale, consider using &lt;a href="https://dev.to/astaykov/access-packages-are-the-control-plane-for-entra-agent-id-permissions-4kph"&gt;Access Packages&lt;/a&gt; to facilitate and manage user assignments for AI agents. Another limitation worth mentioning is that at time of writing of this article, only Agent Identity Blueprints support custom roles definitions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conditional access as authorization policy
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/agent-id" rel="noopener noreferrer"&gt;Conditional access&lt;/a&gt; is a policy engine that controls token acquisition. Unlike app roles or group claims, which are attributes embedded in a token, conditional access is evaluated &lt;em&gt;before&lt;/em&gt; the token is issued. If a policy is violated, the agent does not get a token.&lt;/p&gt;

&lt;p&gt;Here are concrete examples of conditional access for AI agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Custom Security Attribute filters:&lt;/strong&gt; "If the app is tagged with &lt;code&gt;Agents.Approved:True&lt;/code&gt; then allow access." The agent cannot acquire a token unless it is tagged as approved.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Risk-based controls:&lt;/strong&gt; "If Entra's identity protection detects a risky sign-in pattern, require additional verification or deny access." In on-behalf-of type of acces, the user's authentication context and user's offline calculated risks are evaluated (unusual location, impossible travel, leaked credentials, etc.), and access is blocked if that evaluation crosses threshold. In autonomous agent access the agent's risk is evaluated and access is blocked if agent's risk crosses defined threshold. &lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Device compliance:&lt;/strong&gt; "The device must be compliant with Intune policies." If the user is invoking the AI Agents from a device that is not enrolled or is marked non-compliant, the request is blocked.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Conditional access distinguishes two data access patterns that are critical for AI agents:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Autonomous access:&lt;/strong&gt; The agent acts on its own behalf (application flow). Conditional access evaluates the agent's context (where it is running, what device it is on, what time it is).&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;On-behalf-of (OBO) user access:&lt;/strong&gt; The agent acts on behalf of a user. Conditional access evaluates &lt;em&gt;the user's&lt;/em&gt; context (where the user is, their device, their authentication method), not the agent's context.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Critical limitation:&lt;/strong&gt; At the time of writing, Conditional Access accounts for the &lt;em&gt;User&lt;/em&gt; (subject) and &lt;em&gt;Target&lt;/em&gt; (resource), but not the &lt;em&gt;AI Agent&lt;/em&gt; (actor) - when the agents accesses data on-behalf-of the user. This means CA policies cannot directly distinguish between two different agents making requests in the same context - the same user is invoking two different agents, and both happen to require a token to access Work IQ, the conditional access sees these requests identically. &lt;/p&gt;

&lt;p&gt;See &lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/agent-id" rel="noopener noreferrer"&gt;Conditional Access for Agents in Microsoft Entra&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-autonomous-agents?tabs=use-the-enhanced-object-picker" rel="noopener noreferrer"&gt;Recommended policies for autonomous agents&lt;/a&gt; for detailed policy patterns.&lt;/p&gt;

&lt;p&gt;The sharp edge is timing. Conditional access gates token acquisition. If your agent acquires a long-lived token and then starts running outside the approved perimeter, the existing token still works. Conditional access is a gate at the door, not a checkpoint at every step. If you need continuous context verification, you need a different mechanism.&lt;/p&gt;

&lt;h2&gt;
  
  
  App roles as delegated authorization claims
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-apps" rel="noopener noreferrer"&gt;App roles&lt;/a&gt; are a mechanism for the application, or the AI agent, to define its own authorization model. You define a set of roles in your app registration (or Agent Identity Blueprint). You assign users, groups, or service principals to those roles. When the principal authenticates, Entra puts the role membership in the &lt;code&gt;roles&lt;/code&gt; claim of the ID token (or the access token, depending on how you configure the app).&lt;/p&gt;

&lt;p&gt;For a human-facing app, this is straightforward. Your app checks: "Is the user in the &lt;code&gt;Manager&lt;/code&gt; role? If yes, show the budget page. If no, hide it." Entra does the assignment; your app does the enforcement.&lt;/p&gt;

&lt;p&gt;For an AI agent, the model is the same. You can assign humen to a role defined by the Agent Blueprint. In this case, your AI Agent can take authorization decisions for what funcitonality should be avialable to the user. Or, in case of the autonomous agent, you assign the agent's identity to a role defined by downstream API. Then, when your agent acquires a token on behalf of itself (application flow), that token contains the roles assigned to the service principal. Your APIs can inspect the token and make authorization decisions.&lt;/p&gt;

&lt;p&gt;Here is an example. You have an agent that needs to read data from one API and another agent that needs to read and write. You define two app roles in the API app registration: &lt;code&gt;DataReader&lt;/code&gt; and &lt;code&gt;DataWriter&lt;/code&gt;. You assign the read-only agent's service principal to the &lt;code&gt;DataReader&lt;/code&gt; role. You assign the read-write agent to the &lt;code&gt;DataWriter&lt;/code&gt; role. When each agent calls your API, your API code checks the &lt;code&gt;roles&lt;/code&gt; claim. If the token does not include &lt;code&gt;DataWriter&lt;/code&gt;, the write operation fails.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Current UX limitations:&lt;/strong&gt; The Entra Admin Center does not directly expose a UI for defining roles on Agent Identity Blueprints. Roles can be defined and managed via the &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/reference-app-manifest" rel="noopener noreferrer"&gt;app manifest&lt;/a&gt;. Role assignment to users and groups can be done via the Microsoft Graph API or, more scalably, via &lt;a href="https://dev.to/astaykov/access-packages-are-the-control-plane-for-entra-agent-id-permissions-4kph"&gt;Access Packages&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important distinction:&lt;/strong&gt; While the "require user assignment" property exists for Agent Identities, there is no real application scenario where interactive authentication to an Agent happens outside of the Agent Identity Blueprint. The Agent Identity Blueprint is the object that defines the authentication and authorization contract for interactive authentications. The way to add role assignments for an Agent Identity Blueprint is via Access Packages (Add Applications). Requiring explicit role assignment for individual agent identities makes sense in an autonomous agent-to-agent interactions where you want strict controls over the autonomous invokation of AI Agents.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;App-only roles:&lt;/strong&gt; App-only roles are defined on the Blueprint and can be assigned to other agents or clients. When assigned to other workloads, these roles appear in the access token with the Blueprint as the audience (&lt;code&gt;aud&lt;/code&gt; claim). This allows fine-grained authorization delegation between agents.&lt;/p&gt;

&lt;p&gt;The important detail is that &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-configuration" rel="noopener noreferrer"&gt;your protected API must be configured to emit roles in the access token&lt;/a&gt;. This requires a specific configuration step. Many teams forget it. The app roles exist, the assignment exists, but the token arrives empty because the app registration does not emit roles into tokens meant for APIs.&lt;/p&gt;

&lt;p&gt;Also note: app roles are defined per app registration, not globally. If you have two APIs, each needs its own set of roles. There is no central app role store.&lt;/p&gt;

&lt;h2&gt;
  
  
  Group membership claims as attribute-based gates
&lt;/h2&gt;

&lt;p&gt;Group membership claims are different from app roles. When you &lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-autonomous-agents" rel="noopener noreferrer"&gt;emit group claims&lt;/a&gt;, Entra places the groups that the principal belongs to directly into the token.&lt;/p&gt;

&lt;p&gt;Here is the distinction. App roles are defined by the application and assigned by Entra admins within the scope of that app registration. Group claims are based on the broader Entra directory. The principal is a member of the group regardless of the app. The app just asks: "What groups is this principal a member of?" Entra answers by listing them in the token.&lt;/p&gt;

&lt;p&gt;This is useful for attribute-based access control (ABAC). Imagine your organization has groups like &lt;code&gt;Sales-Americas&lt;/code&gt;, &lt;code&gt;Sales-EMEA&lt;/code&gt;, &lt;code&gt;Engineering&lt;/code&gt;, and &lt;code&gt;Finance&lt;/code&gt;. You assign users and workload identities to these groups. Your API receives a token, extracts the &lt;code&gt;groups&lt;/code&gt; claim, and makes decisions based on group membership. "If the token contains the &lt;code&gt;Sales-Americas&lt;/code&gt; group, grant access to the Americas sales data. Otherwise, deny."&lt;/p&gt;

&lt;p&gt;The value of group-based authorization is that it decouples application-specific roles from organizational structure. If you need to add a new group or restructure existing groups, you do not need to change app role definitions. You adjust group membership. The group claim mechanism is still sending the data; your API just interprets it differently.&lt;/p&gt;

&lt;p&gt;There is a configuration step here too. You must tell Entra to emit group claims into tokens for a specific app. By default, groups are not included. Once enabled, Entra sends the group IDs (and optionally display names, but prefer IDs for immutability).&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important: Overage limits and scope configuration.&lt;/strong&gt; When you enable group claims, you must configure scope carefully. Entra has fixed limits on how many group claims can fit in a token:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;OIDC (OpenID Connect):&lt;/strong&gt; 200 groups maximum&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SAML:&lt;/strong&gt; 150 groups maximum&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If a principal belongs to more groups than the limit, Entra sends an "overage claim" instead of individual group IDs. Your API then needs to call Microsoft Graph to fetch the full list. This adds latency and complexity. To avoid overage, &lt;strong&gt;emit only "assigned groups"&lt;/strong&gt; (groups explicitly assigned to the application / blueprint). Be explicit about which groups matter for authorization decisions, and assign only those groups to the target app or agent identity blueprint. This keeps tokens small, predictable, and avoids the overage claim penalty.&lt;/p&gt;

&lt;p&gt;See &lt;a href="https://learn.microsoft.com/en-us/security/zero-trust/develop/configure-tokens-group-claims-app-roles" rel="noopener noreferrer"&gt;Configure group claims and app roles in tokens&lt;/a&gt; for detailed guidance on group claim scope and immutable identifiers.&lt;/p&gt;

&lt;h2&gt;
  
  
  Access packages as the unified control plane
&lt;/h2&gt;

&lt;p&gt;Access packages are where the other mechanisms converge. An &lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/governance/access-packages-create-lifecycle-policies" rel="noopener noreferrer"&gt;access package&lt;/a&gt; is not just governance UI. It is programmable policy choreography. When you assign a principal to an access package, you can configure what happens automatically: group membership additions, app role assignments, Azure role assignments, and so on. All of it coordinated, all of it time-bounded, all of it auditable.&lt;/p&gt;

&lt;p&gt;For an AI agent, access packages are a control plane. You define an access package called "Agent-Production-Access". When you assign an agent's service principal to that package, Entra automatically adds the service principal to the groups that the agent needs, assigns the correct app roles to the agent, and even delegated permissions grants (consent) for downstream APIs and MCP Servers. When the access expires, all of it unwinds. The groups are removed. The app roles are stripped, the consent is undone. The agent loses access.&lt;/p&gt;

&lt;p&gt;This is different from manually assigning groups and roles one by one or going through admin cosnent workflow. With access packages, you are declaring a coherent policy: "This is what production access means for this type of workload. If the workload is approved, give it everything in this package. If it is denied or if the time expires, take everything away." The access package is a unit of authorization. It is atomic in the sense that you do not pick and choose which parts of it to apply. It is an all-or-nothing proposition.&lt;/p&gt;

&lt;p&gt;For enterprises running multiple AI agents, access packages scale the authorization story. You create one access package per authorization context (production, staging, read-only partners, etc.). You assign agents to packages based on their needs. Governance reviews the packages on a schedule and approves or denies. The machinery runs itself.&lt;/p&gt;

&lt;p&gt;There is also integration with approval workflows. An access package can require approval before assignment. You can route approvals to a specific manager or group. You can set expiration dates and schedule re-approval. For AI agents running in production, this is the audit trail that compliance teams expect. "Show me who approved this agent to have access to production data and when that approval expires."&lt;/p&gt;

&lt;h2&gt;
  
  
  Best practices on group emit scope and immutable identifiers
&lt;/h2&gt;

&lt;p&gt;When you enable group claims, you need to decide: should Entra emit &lt;em&gt;all&lt;/em&gt; groups the principal is a member of, or only &lt;em&gt;assigned&lt;/em&gt; groups?&lt;/p&gt;

&lt;p&gt;The answer is clear: &lt;strong&gt;use only groups assigned to the application&lt;/strong&gt;. In a typical enterprise environment, grown over the yers, single user can be member of several thousands of groups. You cannot have a token with six thousand groups in it. And it makes no sense to go over six thousand groups to make authorization decision. Choosing "assigned groups" is tighter and mandatory for production. You explicitly assign the groups relevant for this app to that app. Other groups the principal happens to belong to stay out of the token. This keeps tokens predictable, avoids overage claims, and simplifies authorization logic.&lt;/p&gt;

&lt;p&gt;Immutable identifiers are a separate but equally important consideration. Entra can emit group display names or group IDs in the token. Display names are human-readable but fragile. If a group is renamed, the token still carries the old display name until the token expires. Your API might silently fail to recognize the group and deny access. Or worse - someone may create a group with name that they will grant them access to the app. Group IDs are immutable. A group ID never changes, even if the display name changes. For production authorization logic, prefer group IDs. Your API logic becomes:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if token.groups contains "80d61f4e-4c9e-4f8f-9e1f-2e8f3d5c6b7a":
  grant access
else:
  deny
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Not:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;if token.groups contains "Sales-Americas":
  grant access
else:
  deny
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The first version survives a group rename. The second does not. For large organizations managing many agents and APIs, using immutable group IDs and assigned-only scope is the only way to keep authorization logic maintainable and reliable at scale.&lt;/p&gt;

&lt;h2&gt;
  
  
  Composing the authorization stack
&lt;/h2&gt;

&lt;p&gt;These mechanisms do not compete. They stack. Here is a realistic composition:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;a href="https://docs.azure.cn/en-us/entra/identity/enterprise-apps/what-is-access-management#requiring-user-assignment-for-an-app" rel="noopener noreferrer"&gt;Requiring explicit assignment&lt;/a&gt; gates whether the principal can even attempt authentication.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/overview" rel="noopener noreferrer"&gt;Conditional access policies&lt;/a&gt; check location, device, and other context.&lt;/li&gt;
&lt;li&gt;The token arrives with &lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/howto-add-app-roles-in-apps" rel="noopener noreferrer"&gt;app roles&lt;/a&gt; and &lt;a href="https://learn.microsoft.com/en-us/entra/identity/hybrid/connect/how-to-connect-fed-group-claims" rel="noopener noreferrer"&gt;group claims&lt;/a&gt; embedded.&lt;/li&gt;
&lt;li&gt;Your API inspects the token, checks app roles and groups, and makes fine-grained decisions.&lt;/li&gt;
&lt;li&gt;All of this is orchestrated through an &lt;a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-access-packages" rel="noopener noreferrer"&gt;access package&lt;/a&gt; that defines the coherent policy for a given context (production, partner, read-only, etc.).&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each layer has a different trust surface. User assignment is coarse. Conditional access is context-based. App roles and groups are attribute-based. Access packages are policy-coordinated. Together, they form a defense in depth.&lt;/p&gt;

&lt;p&gt;When you are designing authorization for an AI agent, ask yourself:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Should this agent be able to authenticate at all right now? (User assignment)&lt;/li&gt;
&lt;li&gt;Under what conditions (location, device, time) should token acquisition be allowed? (Conditional access)&lt;/li&gt;
&lt;li&gt;If the token is issued, what roles and group memberships should it carry? (App roles and group claims)&lt;/li&gt;
&lt;li&gt;How is the agent's access provisioned, reviewed, and revoked? (Access packages and lifecycle management)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you can answer each question with a specific, enforceable policy, you have authorization architecture. If you are relying on the API layer alone to deny requests, you are carrying unnecessary risk.&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/azure/active-directory/manage-apps/assign-user-or-group-access-portal" rel="noopener noreferrer"&gt;User assignment to enterprise applications&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/agent-id" rel="noopener noreferrer"&gt;Conditional Access for Agents in Microsoft Entra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity/conditional-access/policy-autonomous-agents?tabs=use-the-enhanced-object-picker" rel="noopener noreferrer"&gt;Recommended policies for autonomous agents in Microsoft Entra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/howto-add-app-roles-in-apps" rel="noopener noreferrer"&gt;How to add app roles in apps&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/entra/identity-platform/reference-app-manifest" rel="noopener noreferrer"&gt;Understanding the app manifest&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/scenario-protected-web-api-app-configuration" rel="noopener noreferrer"&gt;Protected Web API app configuration&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/security/zero-trust/develop/configure-tokens-group-claims-app-roles" rel="noopener noreferrer"&gt;Configure group claims and app roles in tokens&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/en-us/azure/active-directory/develop/how-to-emit-saml-claims-based-on-application-roles" rel="noopener noreferrer"&gt;How to emit SAML claims based on application roles&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>Flexibility with admin consent in Entra: how to scale consent flows for AI agents</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Tue, 25 Aug 2026 11:43:00 +0000</pubDate>
      <link>https://dev.to/astaykov/flexibility-with-admin-consent-in-entra-how-to-scale-consent-flows-for-ai-agents-2h1p</link>
      <guid>https://dev.to/astaykov/flexibility-with-admin-consent-in-entra-how-to-scale-consent-flows-for-ai-agents-2h1p</guid>
      <description>&lt;p&gt;Most organizations treat tenant-wide admin consent in Microsoft Entra as a binary decision. Either a central identity team approves every request, or somebody receives a broad administrative role so the queue can move faster.&lt;/p&gt;

&lt;p&gt;That is a false choice.&lt;/p&gt;

&lt;p&gt;Microsoft Entra supports a more precise control model: define which consent requests are acceptable in an app consent policy, bind that policy to a custom directory role, and delegate only the authority to approve requests inside that envelope. The role action that makes this possible is &lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/custom-consent-permissions#granting-permissions-to-apps-on-behalf-of-all-admin-consent" rel="noopener noreferrer"&gt;&lt;code&gt;microsoft.directory/servicePrincipals/managePermissionGrantsForAll.{id}&lt;/code&gt;&lt;/a&gt;, where &lt;code&gt;{id}&lt;/code&gt; identifies the policy that constrains the consent authority.&lt;/p&gt;

&lt;p&gt;That is not a smaller version of Global Administrator. It is a different architectural idea: &lt;em&gt;delegate the decision without delegating the directory&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;AI agents make this distinction urgent. As organizations create more agent identities and authorize them against Microsoft Graph and other APIs, consent stops being an occasional setup task. It becomes a recurring control-plane operation. Scaling that operation with broad roles is the identity equivalent of solving traffic congestion by issuing everyone a master key.&lt;/p&gt;

&lt;h2&gt;
  
  
  The usual admin consent model does not scale
&lt;/h2&gt;

&lt;p&gt;Tenant-wide admin consent is deliberately sensitive. A grant can authorize an application to access organizational data or perform privileged operations across the tenant, so Microsoft recommends reviewing the requested permissions carefully before granting it (&lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/grant-admin-consent" rel="noopener noreferrer"&gt;grant tenant-wide admin consent&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The operational response is usually centralization. Every request lands with a small identity or security team. That team validates the application, checks the requested permissions, finds the business owner, and either approves or rejects the request.&lt;/p&gt;

&lt;p&gt;Centralization gives you consistency, but it also creates a queue. The queue grows with every SaaS integration, internal application, automation workload, and AI agent. Eventually the organization reaches for a built-in role such as Application Administrator or Cloud Application Administrator to distribute the work.&lt;/p&gt;

&lt;p&gt;That relieves the queue by expanding authority. It does not make the consent decision more precise.&lt;/p&gt;

&lt;p&gt;Microsoft Entra already has a control surface for that precision. It is just underused.&lt;/p&gt;

&lt;h2&gt;
  
  
  An app consent policy is a permission envelope
&lt;/h2&gt;

&lt;p&gt;An &lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/manage-app-consent-policies" rel="noopener noreferrer"&gt;app consent policy&lt;/a&gt; describes the conditions under which a consent event is allowed. A policy contains one or more include condition sets and can contain exclude condition sets. A request must match at least one include condition set and must not match any exclude condition set for the policy to pass.&lt;/p&gt;

&lt;p&gt;That evaluation model matters. You are not handing a delegate a list of instructions and hoping they follow it. You are placing a policy boundary in the authorization path.&lt;/p&gt;

&lt;p&gt;The condition sets can describe characteristics such as the permission type, the resource application, specific permissions, client applications, and publisher status (&lt;a href="https://learn.microsoft.com/graph/api/resources/permissiongrantconditionset?view=graph-rest-1.0" rel="noopener noreferrer"&gt;permission grant condition set resource&lt;/a&gt;). This lets an organization express consent envelopes such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;delegated &lt;code&gt;Mail.Read&lt;/code&gt; and &lt;code&gt;Files.Read&lt;/code&gt; permissions for Microsoft Graph,&lt;/li&gt;
&lt;li&gt;selected application permissions exposed by one internal finance API,&lt;/li&gt;
&lt;li&gt;permissions requested only by explicitly identified client applications,&lt;/li&gt;
&lt;li&gt;delegated permissions from verified publishers, with sensitive resources excluded.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important word is &lt;em&gt;selected&lt;/em&gt;. The policy does not need to authorize every permission exposed by an API. It can describe the exact delegated scopes and application roles the organization has decided are appropriate for this delegation model (&lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/manage-app-consent-policies#app-consent-policy-segments" rel="noopener noreferrer"&gt;manage app consent policies&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Today, Microsoft exposes this policy model through Microsoft Graph. The relevant resources are &lt;code&gt;permissionGrantPolicy&lt;/code&gt; and &lt;code&gt;permissionGrantConditionSet&lt;/code&gt;, exposed under &lt;code&gt;/policies/permissionGrantPolicies&lt;/code&gt; (&lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/manage-app-consent-policies#manage-app-consent-policies" rel="noopener noreferrer"&gt;manage app consent policies with Microsoft Graph&lt;/a&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  The custom role binds authority to the envelope
&lt;/h2&gt;

&lt;p&gt;The policy defines what may be approved. The custom role defines who may approve it.&lt;/p&gt;

&lt;p&gt;Microsoft documents a specific custom-role permission for tenant-wide admin consent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;microsoft.directory/servicePrincipals/managePermissionGrantsForAll.{id}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The &lt;code&gt;{id}&lt;/code&gt; suffix is not decoration. It binds the role permission to an app consent policy. The role assignee can grant tenant-wide consent for delegated permissions and application permissions only when the consent request satisfies that policy (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/custom-consent-permissions#granting-permissions-to-apps-on-behalf-of-all-admin-consent" rel="noopener noreferrer"&gt;app consent permissions for custom roles&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That gives architects a much better delegation primitive than "make this person a Cloud Application Administrator." The delegate receives a defined consent capability, not general application administration authority.&lt;/p&gt;

&lt;p&gt;For example, a data platform team could receive authority to grant a fixed set of delegated scopes against an internal analytics API. A Microsoft 365 integration team could receive a different role tied to a policy for a small set of approved Graph permissions. Neither team needs a role whose authority extends across unrelated application-management operations.&lt;/p&gt;

&lt;p&gt;Custom roles and app consent permissions require Microsoft Entra ID P1. The Entra admin center does not currently support adding these app-consent permissions to a custom role definition, so this configuration is available through Microsoft Graph rather than the Entra admin center UX (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/custom-consent-permissions" rel="noopener noreferrer"&gt;license and tooling requirements&lt;/a&gt;). That is implementation friction, but it does not weaken the control model.&lt;/p&gt;

&lt;h2&gt;
  
  
  Policy authorship and consent execution are different jobs
&lt;/h2&gt;

&lt;p&gt;The strongest version of this architecture separates two responsibilities.&lt;/p&gt;

&lt;p&gt;The central identity or security team authors the permission envelope. It decides which resource applications, delegated scopes, application roles, client properties, and exclusions belong in the policy. That team also controls the custom role definition and its assignments through the documented Microsoft Entra role-management interfaces (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/custom-create" rel="noopener noreferrer"&gt;create a custom role&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The delegated administrator evaluates individual requests and grants consent only when Entra determines that the request fits the referenced policy.&lt;/p&gt;

&lt;p&gt;Those are not the same privilege. Microsoft exposes separate custom-role permissions for creating, reading, updating, and deleting permission grant policies, distinct from &lt;code&gt;managePermissionGrantsForAll.{id}&lt;/code&gt; (&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/custom-consent-permissions#managing-app-consent-policies" rel="noopener noreferrer"&gt;managing app consent policies through custom roles&lt;/a&gt;). A consent delegate should not automatically receive policy-authoring permissions.&lt;/p&gt;

&lt;p&gt;That separation is the control.&lt;/p&gt;

&lt;p&gt;If the same operator can widen the allowlist and approve the newly allowed request, the policy becomes a speed bump. If one team defines the envelope and another operates inside it, the policy becomes an enforceable delegation boundary.&lt;/p&gt;

&lt;h2&gt;
  
  
  Watch for alternate paths around the policy
&lt;/h2&gt;

&lt;p&gt;There is one architectural trap worth stating plainly: app consent policies are not the only authorization mechanism that can allow a principal to grant permissions.&lt;/p&gt;

&lt;p&gt;Microsoft documents that policies are evaluated independently and that a principal needs only one authorization path that permits a consent event. Ownership of a resource service principal and broad Microsoft Graph application permissions can provide other ways to create grants in some scenarios (&lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/manage-app-consent-policies#multiple-policies-or-authorization-mechanisms-to-grant-consent" rel="noopener noreferrer"&gt;multiple policies or authorization mechanisms&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That means the restricted role is only as restricted as the delegate's &lt;em&gt;effective&lt;/em&gt; authority.&lt;/p&gt;

&lt;p&gt;Do not combine the policy-bound consent role with broad application-management permissions and then describe the result as least privilege. Review the delegate's other directory roles, Microsoft Graph permissions, ownership assignments, and group-derived access. The consent policy should be the intended authorization path, not one path among several wider ones.&lt;/p&gt;

&lt;p&gt;This is also why role naming matters. "Finance API Consent Approver" communicates a bounded function. "Application Admin Lite" invites future privilege creep. Names do not enforce security, but they tell future operators whether a role is a contract or a convenience.&lt;/p&gt;

&lt;h2&gt;
  
  
  A domain-based consent operating model
&lt;/h2&gt;

&lt;p&gt;The practical enterprise model is not one policy for every application and not one policy for the entire tenant. Both extremes create maintenance problems.&lt;/p&gt;

&lt;p&gt;Define envelopes around stable trust boundaries. A boundary might be a business domain, an API platform, a class of low-impact delegated permissions, or a controlled set of client applications. The condition-set model supports matching on multiple properties, so the policy can reflect more than a permission name alone (&lt;a href="https://learn.microsoft.com/graph/api/resources/permissiongrantconditionset?view=graph-rest-1.0" rel="noopener noreferrer"&gt;permission grant condition set properties&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;Then assign the corresponding custom role to administrators who understand that domain. They can evaluate business purpose and operational context without receiving unrelated directory authority.&lt;/p&gt;

&lt;p&gt;This changes the central identity team's job. It stops being the human endpoint for every consent request and becomes the designer of consent boundaries. The team owns policy quality, role lifecycle, exception handling, and periodic review. Domain administrators operate within the encoded boundary.&lt;/p&gt;

&lt;p&gt;The result is not decentralized consent without governance. It is &lt;em&gt;federated consent with a centrally defined authorization ceiling&lt;/em&gt;.&lt;/p&gt;

&lt;h2&gt;
  
  
  AI agents turn a useful feature into an architectural requirement
&lt;/h2&gt;

&lt;p&gt;Microsoft Entra Agent ID supports delegated and application permission models for agents. Delegated authorization is represented by an &lt;code&gt;oAuth2PermissionGrant&lt;/code&gt;, while application authorization is represented by an &lt;code&gt;appRoleAssignment&lt;/code&gt;; Microsoft also documents direct creation of these objects and consent-based flows for granting agents access (&lt;a href="https://learn.microsoft.com/entra/agent-id/grant-agent-access-microsoft-365" rel="noopener noreferrer"&gt;grant agents access to Microsoft 365 resources&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That means agent adoption feeds the same consent control plane already used for applications, but at a different operational scale. A blueprint may produce many agent identities, and different agents may need different access as they move from experimentation into production (&lt;a href="https://learn.microsoft.com/entra/agent-id/key-concepts" rel="noopener noreferrer"&gt;Agent ID key concepts&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;The wrong response is to multiply broadly privileged administrators at the same rate.&lt;/p&gt;

&lt;p&gt;The better response is to decide which permission combinations are routine, defensible, and delegable, then encode those combinations in consent policies. High-risk or unusual requests stay with the central authority. Known patterns move through policy-bound delegates.&lt;/p&gt;

&lt;p&gt;This complements the argument in &lt;a href="https://dev.to/astaykov/why-consent-is-the-trust-contract-for-entra-agent-id-c03"&gt;Why consent is the trust contract for Entra Agent ID&lt;/a&gt;. Consent still answers who authorized an agent, for which resource, and with what permissions. Granular delegation answers the next operational question: who is trusted to make that authorization decision without receiving power over the rest of the tenant?&lt;/p&gt;

&lt;p&gt;There is an important current boundary. Microsoft states that custom Microsoft Entra roles cannot be assigned to agent identities (&lt;a href="https://learn.microsoft.com/entra/agent-id/authorization-agent-id#microsoft-entra-role-assignments-for-agent-identities" rel="noopener noreferrer"&gt;Microsoft Entra role assignments for agent identities&lt;/a&gt;). This pattern therefore does not make an AI agent the holder of the restricted consent role. It makes the consent process around AI agents more scalable for supported human role assignees.&lt;/p&gt;

&lt;h2&gt;
  
  
  The question this architecture creates
&lt;/h2&gt;

&lt;p&gt;Once consent policy becomes an enforceable envelope, a more interesting question appears.&lt;/p&gt;

&lt;p&gt;What if an AI agent could assess a consent request, inspect the requested permissions and business context, compare the request with organizational policy, and recommend approval or rejection? What if, one day, the platform supported that agent making the decision inside a boundary it could not rewrite?&lt;/p&gt;

&lt;p&gt;That is not the capability described here. Current Agent IDs cannot hold custom Entra roles, and a probabilistic reasoning system should not be mistaken for an authorization boundary (&lt;a href="https://learn.microsoft.com/entra/agent-id/authorization-agent-id" rel="noopener noreferrer"&gt;Agent ID authorization restrictions&lt;/a&gt;). But granular consent delegation gives that future discussion a serious starting point. The policy remains deterministic. The permission ceiling remains centrally owned. The assessment could become intelligent.&lt;/p&gt;

&lt;p&gt;The immediate opportunity is less speculative and more valuable: stop treating admin consent as authority that can only be centralized or broadly delegated. Microsoft Entra already lets you define the exact consent envelope and hand out only the key that fits that lock.&lt;/p&gt;

&lt;p&gt;As AI agents increase the volume and variety of consent decisions, that flexibility stops being an obscure role-management feature. It becomes part of the architecture.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/manage-app-consent-policies" rel="noopener noreferrer"&gt;Manage app consent policies&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/api/resources/permissiongrantconditionset?view=graph-rest-1.0" rel="noopener noreferrer"&gt;Permission grant condition set resource type&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/custom-consent-permissions" rel="noopener noreferrer"&gt;App consent permissions for custom roles 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/custom-create" rel="noopener noreferrer"&gt;Create a custom role in Microsoft Entra ID&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity/enterprise-apps/grant-admin-consent" rel="noopener noreferrer"&gt;Grant tenant-wide admin consent to an application&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/graph/permissions-grant-via-msgraph" rel="noopener noreferrer"&gt;Grant or revoke API permissions programmatically&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/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/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/entra/agent-id/key-concepts" rel="noopener noreferrer"&gt;Microsoft Entra Agent ID key concepts&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>aiagents</category>
      <category>zerotrust</category>
      <category>microsoftentra</category>
      <category>cloudsecurity</category>
    </item>
    <item>
      <title>Dynamic client registration and CIMD are not a trust framework</title>
      <dc:creator>Anton Staykov</dc:creator>
      <pubDate>Mon, 17 Aug 2026 10:37:00 +0000</pubDate>
      <link>https://dev.to/astaykov/dynamic-client-registration-and-cimd-are-not-a-trust-framework-fh7</link>
      <guid>https://dev.to/astaykov/dynamic-client-registration-and-cimd-are-not-a-trust-framework-fh7</guid>
      <description>&lt;p&gt;The MCP specification ships with a strong opinion about how AI clients should onboard with authorization servers: dynamically, at runtime, without pre-coordination (&lt;a href="https://modelcontextprotocol.io/specification/latest/basic/authorization" rel="noopener noreferrer"&gt;MCP authorization spec&lt;/a&gt;). The AI community has largely nodded along. Enterprise security teams have largely not been consulted.&lt;/p&gt;

&lt;p&gt;That mismatch is the subject of this article.&lt;/p&gt;

&lt;p&gt;DCR and CIMD are genuinely useful protocol mechanisms. They are not a trust framework. And in an era where supply chain attacks are the dominant enterprise threat vector, confusing the two is a governance failure waiting for a specific client name to trigger it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What DCR was designed to remove
&lt;/h2&gt;

&lt;p&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;RFC 7591 - OAuth 2.0 Dynamic Client Registration Protocol&lt;/a&gt; solves a real problem. The traditional OAuth onboarding sequence requires a developer to pre-register their client application with the authorization server, receive a &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt;, configure redirect URIs, and coordinate with whoever manages the identity provider before a single token can flow. For SaaS products, open developer ecosystems, and multi-tenant platforms, this ceremony is expensive. Teams wait days for provisioning. Integration partners hit identity-team backlogs. CI pipelines stall on human approvals.&lt;/p&gt;

&lt;p&gt;DCR automates the ceremony. The client POSTs a JSON body describing itself to the authorization server's &lt;code&gt;registration_endpoint&lt;/code&gt;. The server returns a &lt;code&gt;client_id&lt;/code&gt; and &lt;code&gt;client_secret&lt;/code&gt; immediately. No human approval. No pre-coordination. Developer velocity restored.&lt;/p&gt;

&lt;p&gt;That is a genuine improvement in the right context. Public MCP servers, open developer tooling, and hobbyist integrations benefit from it directly. The friction reduction is real, and dismissing it is wrong.&lt;/p&gt;

&lt;p&gt;The problem is not the protocol. The problem is the assumption that registration equals authorization, and that authorization equals trust.&lt;/p&gt;

&lt;h2&gt;
  
  
  What CIMD adds to the picture
&lt;/h2&gt;

&lt;p&gt;Client Information Meta Data (CIMD) refers to the structured metadata fields a client submits in the DCR request, and through &lt;a href="https://datatracker.ietf.org/doc/html/rfc7592" rel="noopener noreferrer"&gt;RFC 7592 (Dynamic Client Registration Management)&lt;/a&gt;, the management endpoint that lets a registered client later read back and update its own registration record.&lt;/p&gt;

&lt;p&gt;The metadata fields are richer than they appear at first. A client can declare its &lt;code&gt;client_name&lt;/code&gt;, &lt;code&gt;client_uri&lt;/code&gt;, &lt;code&gt;redirect_uris&lt;/code&gt;, &lt;code&gt;grant_types&lt;/code&gt;, &lt;code&gt;scope&lt;/code&gt;, &lt;code&gt;logo_uri&lt;/code&gt;, and most interestingly a &lt;code&gt;software_statement&lt;/code&gt; (&lt;a href="https://datatracker.ietf.org/doc/html/rfc7591#section-2.3" rel="noopener noreferrer"&gt;RFC 7591 §2.3&lt;/a&gt;). A software statement is a signed JWT issued by a third party the authorization server trusts, asserting facts about the client software: who built it, what version it is, what it is intended to do. It is the closest thing RFC 7591 has to attestation.&lt;/p&gt;

&lt;p&gt;CIMD improves signal quality. It gives authorization servers structured, machine-readable metadata to work with. It makes client inventory possible in principle: if you log registrations, you at least know the name each client claimed. Software statements, when an authorization server validates them against a trusted issuer, move the model from "self-asserted identity" to "third-party asserted identity."&lt;/p&gt;

&lt;p&gt;The critical word is &lt;em&gt;when&lt;/em&gt;. Software statement validation is optional in RFC 7591. Most MCP server implementations do not enforce it. A client that omits the &lt;code&gt;software_statement&lt;/code&gt; field registers exactly as successfully as one that includes it. The registration endpoint cannot distinguish a vetted corporate tool from a novel command-line script someone assembled over a weekend and pointed at your server.&lt;/p&gt;

&lt;p&gt;CIMD raises the quality of information available at registration time. It does not answer whether your organization approved this client to exist here.&lt;/p&gt;

&lt;h2&gt;
  
  
  The enterprise security gap
&lt;/h2&gt;

&lt;p&gt;Security teams in regulated industries (financial services, healthcare, critical infrastructure, government) operate on a fundamentally different assumption about software trust than DCR's default model. They run approved-software programs. Patch management is gated on testing and change-board approval. In the most security-mature environments I have worked in across thousands of enterprise security conversations, even Windows Updates are staged, validated, and controlled before rollout to the fleet.&lt;/p&gt;

&lt;p&gt;This is not bureaucratic paranoia. It is the appropriate operational posture given how supply chain attacks actually work.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://www.cisa.gov/news-events/news/cisa-insights-solarwinds" rel="noopener noreferrer"&gt;SolarWinds&lt;/a&gt; demonstrated that a trusted vendor's signed software update is a viable attack vehicle. &lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-3094" rel="noopener noreferrer"&gt;CVE-2024-3094 (XZ Utils)&lt;/a&gt; showed that a two-year social engineering campaign can insert a backdoor into a compression library that ships inside essentially every Linux distribution. The lesson from both is not "trust nothing." The lesson is that &lt;em&gt;how you establish and revoke software trust matters as much as whether a signature is present&lt;/em&gt;, and that self-asserted identity for software is precisely the attack surface adversaries aim for.&lt;/p&gt;

&lt;p&gt;Against that backdrop, consider what unrestricted DCR on an enterprise MCP server looks like. Any AI client (any command-line tool, any agentic orchestrator, any LLM-driven system) that discovers the server's &lt;code&gt;registration_endpoint&lt;/code&gt; can register itself, receive credentials, and start calling your server's tools. The server knows the client's claimed &lt;code&gt;client_name&lt;/code&gt;. It does not know whether your security team reviewed the client's code, whether the vendor passed your procurement process, whether the version running is the one that was reviewed, or whether the registration request is originating from inside your network perimeter or from an endpoint that was compromised last week.&lt;/p&gt;

&lt;p&gt;The SOC analyst's question is not theoretical: "Show me every MCP client registered on this server, who approved each one, under which policy, and when that approval was granted." Unrestricted DCR makes that question unanswerable, or worse, the answer is "anyone who sent a POST to the right endpoint."&lt;/p&gt;

&lt;p&gt;Small organizations face the same question. The compliance regime may be less formal, but the threat is not.&lt;/p&gt;

&lt;h2&gt;
  
  
  Entra's deliberate answer: explicit registration as a trust decision
&lt;/h2&gt;

&lt;p&gt;This is where the AI community tends to frame a gap. It is not a gap. It is architecture.&lt;/p&gt;

&lt;p&gt;Microsoft Entra ID has no &lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;RFC 7591&lt;/a&gt; &lt;code&gt;registration_endpoint&lt;/code&gt;. There is no URL on &lt;code&gt;login.microsoftonline.com&lt;/code&gt; that accepts a DCR POST and returns &lt;code&gt;client_id&lt;/code&gt; credentials at runtime. Every application that can receive Entra-issued tokens must be explicitly registered, through the Azure portal, the Microsoft Graph API, the Azure CLI, or ARM templates, by a principal holding at minimum the &lt;a href="https://learn.microsoft.com/entra/identity/role-based-access-control/permissions-reference#application-developer" rel="noopener noreferrer"&gt;Application Developer role&lt;/a&gt; (&lt;a href="https://learn.microsoft.com/entra/identity-platform/quickstart-register-app" rel="noopener noreferrer"&gt;register an application in Microsoft Entra ID&lt;/a&gt;).&lt;/p&gt;

&lt;p&gt;That deliberate registration is not the end of the trust chain. It is the beginning of it. A registered application that has not been explicitly consented still receives no tokens. Consent in Entra is the authorization gate: the act that says "this application is authorized to call this API, with these permissions, under this policy." A well-named AI client with a convincing &lt;code&gt;client_name&lt;/code&gt; and a polished &lt;code&gt;client_uri&lt;/code&gt; gets nothing from Entra without a human trust decision in the chain. (For the full governance architecture behind this claim, see the article &lt;a href="https://dev.to/astaykov/why-consent-is-the-trust-contract-for-entra-agent-id-c03"&gt;Why consent is the trust contract for Entra Agent ID&lt;/a&gt;.)&lt;/p&gt;

&lt;p&gt;The &lt;a href="https://modelcontextprotocol.io/specification/latest/basic/authorization" rel="noopener noreferrer"&gt;MCP authorization specification&lt;/a&gt; is careful here: it states that clients SHOULD attempt DCR when the authorization server's &lt;a href="https://datatracker.ietf.org/doc/html/rfc8414" rel="noopener noreferrer"&gt;RFC 8414&lt;/a&gt; metadata includes a &lt;code&gt;registration_endpoint&lt;/code&gt;. "Should," not "must." A well-behaved MCP client encountering an authorization server that publishes no &lt;code&gt;registration_endpoint&lt;/code&gt; (which is what every Entra tenant presents) falls back to static registration. That fallback is the intended behavior. The friction is intentional, and its source is a governance posture, not a missing feature.&lt;/p&gt;

&lt;p&gt;The honest tradeoff: Entra's model maximizes governance precision and eliminates runtime surprise about who can request tokens. It creates real friction for MCP clients that expect to self-register on first contact. That friction is a feature for regulated enterprise deployments. It is a genuine obstacle for open, developer-facing MCP servers where the audience is trusted developers and the stakes per registration are low. Both statements are true simultaneously.&lt;/p&gt;

&lt;h2&gt;
  
  
  APIM AI Gateway: the bridge, and where the gap remains
&lt;/h2&gt;

&lt;p&gt;When you place &lt;a href="https://learn.microsoft.com/azure/api-management/genai-gateway-capabilities" rel="noopener noreferrer"&gt;Azure API Management's AI Gateway&lt;/a&gt; in front of your MCP server, you gain a set of controls that together approach what a governance-controlled DCR layer would look like, without implementing one. Understanding exactly what APIM provides, and what it does not, is what allows you to design the right architecture rather than assume a gap has been closed when it has not.&lt;/p&gt;

&lt;p&gt;The first piece is discovery integration. APIM can expose the &lt;code&gt;.well-known/oauth-protected-resource&lt;/code&gt; endpoint defined in &lt;a href="https://datatracker.ietf.org/doc/html/rfc9728" rel="noopener noreferrer"&gt;RFC 9728&lt;/a&gt;, which tells DCR-capable MCP clients where to find the authorization server protecting this resource (&lt;a href="https://learn.microsoft.com/azure/api-management/secure-mcp-servers" rel="noopener noreferrer"&gt;secure access to MCP servers&lt;/a&gt;). When APIM points to Entra as the authorization server, the MCP client reaches Entra, discovers no &lt;code&gt;registration_endpoint&lt;/code&gt; in Entra's RFC 8414 metadata, and falls back to static registration. The discovery flow runs correctly. The governance gate holds. APIM participates in the DCR protocol dance without becoming a DCR endpoint.&lt;/p&gt;

&lt;p&gt;The second piece is runtime client enforcement via the &lt;a href="https://learn.microsoft.com/azure/api-management/validate-azure-ad-token-policy" rel="noopener noreferrer"&gt;&lt;code&gt;validate-azure-ad-token&lt;/code&gt; policy&lt;/a&gt;. This policy accepts a &lt;code&gt;&amp;lt;client-application-ids&amp;gt;&lt;/code&gt; block, an explicit allowlist of Entra application IDs whose tokens are valid at this gateway:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight xml"&gt;&lt;code&gt;&lt;span class="nt"&gt;&amp;lt;validate-azure-ad-token&lt;/span&gt; &lt;span class="na"&gt;tenant-id=&lt;/span&gt;&lt;span class="s"&gt;"your-entra-tenant-id"&lt;/span&gt;
    &lt;span class="na"&gt;header-name=&lt;/span&gt;&lt;span class="s"&gt;"Authorization"&lt;/span&gt;
    &lt;span class="na"&gt;failed-validation-httpcode=&lt;/span&gt;&lt;span class="s"&gt;"401"&lt;/span&gt;
    &lt;span class="na"&gt;failed-validation-error-message=&lt;/span&gt;&lt;span class="s"&gt;"Unauthorized. Access token is missing or invalid."&lt;/span&gt;&lt;span class="nt"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;client-application-ids&amp;gt;&lt;/span&gt;
        &lt;span class="nt"&gt;&amp;lt;application-id&amp;gt;&lt;/span&gt;approved-mcp-client-app-id&lt;span class="nt"&gt;&amp;lt;/application-id&amp;gt;&lt;/span&gt;
    &lt;span class="nt"&gt;&amp;lt;/client-application-ids&amp;gt;&lt;/span&gt;
&lt;span class="nt"&gt;&amp;lt;/validate-azure-ad-token&amp;gt;&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A client that was registered in Entra and consented (both explicit human decisions), but whose application ID is absent from this list, still cannot pass the gateway. That is two distinct human approval steps between "an AI client exists" and "that AI client can invoke your MCP server tools." This is the runtime equivalent of an enterprise approved-software list, enforced at the gateway rather than in application code.&lt;/p&gt;

&lt;p&gt;For scenarios where Entra registration still introduces too much friction (third-party client integrations, short-lived automation, partner tooling), APIM's subscription key model is the highest-control alternative. Every MCP client consumer receives a key provisioned explicitly by an APIM administrator. No dynamic discovery. No self-registration. Full explicit provisioning per consumer, with gateway-level revocation that does not wait for a consent withdrawal to propagate through the identity layer.&lt;/p&gt;

&lt;p&gt;The observability dimension is underweighted in most governance architecture conversations. APIM routes all MCP tool invocations through a single plane with &lt;a href="https://learn.microsoft.com/azure/api-management/api-management-howto-app-insights" rel="noopener noreferrer"&gt;Application Insights integration&lt;/a&gt; and the &lt;a href="https://learn.microsoft.com/azure/api-management/llm-emit-token-metric-policy" rel="noopener noreferrer"&gt;&lt;code&gt;llm-emit-token-metric&lt;/code&gt; policy&lt;/a&gt; for per-consumer token tracking. The SOC analyst's question, "show me every client calling this server, what it consumed, and when", becomes answerable from a single observability plane. That audit trail is precisely what unrestricted DCR alone never provides.&lt;/p&gt;

&lt;p&gt;The honest gap deserves explicit naming. Neither APIM nor Entra today offers a governance-controlled RFC 7591 registration endpoint: a &lt;code&gt;/register&lt;/code&gt; that issues credentials only to clients that have passed a policy gate: an Entra consent check, an access package approval, or a security review workflow. APIM and Entra together approximate this behavior through a different mechanism: explicit Entra app registration as the consent prerequisite, gateway policy as the runtime enforcement layer. The interoperability friction with DCR-native MCP clients is real. The architectural slot for a "governed DCR endpoint" is currently unoccupied in the Microsoft stack.&lt;/p&gt;

&lt;p&gt;That gap is worth naming because it is where the tooling will likely evolve. An architecture where APIM exposes an RFC 7591-compliant &lt;code&gt;/register&lt;/code&gt; endpoint that, on receiving a DCR request, triggers an Entra app registration, initiates an admin consent workflow, and optionally assigns an access package, before returning credentials to the registering client, would close it elegantly. That architecture does not exist today as a native product feature. What exists is the layered governance model described above.&lt;/p&gt;

&lt;h2&gt;
  
  
  A governance stack, not a binary choice
&lt;/h2&gt;

&lt;p&gt;The practical architecture decision is not "DCR or no DCR." It is: what governance controls stand between a client expressing interest and a client receiving credentials?&lt;/p&gt;

&lt;p&gt;For open, developer-facing MCP servers where the audience is trusted developers, the data sensitivity is low, and the expected client population is diverse and changing, DCR with software statement validation and a lean consent model is appropriate. Lean does not mean absent: even here, logging registrations, validating software statements when present, and auditing active clients periodically are the minimum defensible posture.&lt;/p&gt;

&lt;p&gt;For enterprise MCP servers that expose organizational data, internal APIs, financial workflows, or records subject to regulatory requirements, the bar is different. Every client should be named. Every registration should be a deliberate trust decision. Every token scope should be consented explicitly under a policy that your security and governance teams own.&lt;/p&gt;

&lt;p&gt;APIM plus Entra delivers that model today. Entra's explicit registration and consent layer ensures no client receives tokens without a human decision in the chain. APIM's AI Gateway enforcement layer ensures no token-bearing client bypasses your runtime policy. The observability layer ensures that "who approved this, with which permissions, and when" remains answerable across the full client lifecycle.&lt;/p&gt;

&lt;p&gt;CIMD metadata enriches the picture on the way in: software statements, declared grant types, client URIs. Use them where your authorization server or gateway policy can act on them. Do not treat "richer metadata at registration time" as a substitute for governance state. Those are different properties, and conflating them is the precise mistake that makes a SOC analyst's life difficult six months after a pilot launches.&lt;/p&gt;

&lt;p&gt;DCR is useful. CIMD is useful. Neither is a trust framework. The security teams that built enterprise software approval programs did not do it for the friction. They did it because supply chain attacks are real, and the moment you stop asking "who approved this software to exist here" is the moment a well-named binary with a convincing &lt;code&gt;client_name&lt;/code&gt; receives credentials to your organizational data.&lt;/p&gt;

&lt;p&gt;APIM AI Gateway and Entra's explicit registration model is the closest the Microsoft stack comes today to answering that question at MCP server scale. The remaining gap is a named target. The governance floor is already in place.&lt;/p&gt;

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

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7591" rel="noopener noreferrer"&gt;RFC 7591 - OAuth 2.0 Dynamic Client Registration Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc7592" rel="noopener noreferrer"&gt;RFC 7592 - OAuth 2.0 Dynamic Client Registration Management Protocol&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc9728" rel="noopener noreferrer"&gt;RFC 9728 - OAuth 2.0 Protected Resource Metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://datatracker.ietf.org/doc/html/rfc8414" rel="noopener noreferrer"&gt;RFC 8414 - OAuth 2.0 Authorization Server Metadata&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://modelcontextprotocol.io/specification/latest/basic/authorization" rel="noopener noreferrer"&gt;MCP authorization specification&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/quickstart-register-app" rel="noopener noreferrer"&gt;Register an application 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/permissions-reference#application-developer" rel="noopener noreferrer"&gt;Application Developer role - Microsoft Entra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/microsoft-365/copilot/extensibility/plugin-authentication-dynamic-client-registration" rel="noopener noreferrer"&gt;Configure dynamic client registration - Microsoft 365 Copilot extensibility&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/api-management/genai-gateway-capabilities" rel="noopener noreferrer"&gt;AI gateway capabilities in Azure API Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/api-management/secure-mcp-servers" rel="noopener noreferrer"&gt;Secure access to MCP servers in Azure API Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/api-management/mcp-server-overview" rel="noopener noreferrer"&gt;MCP server overview in Azure API Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/api-management/validate-azure-ad-token-policy" rel="noopener noreferrer"&gt;validate-azure-ad-token policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/api-management/credentials-overview" rel="noopener noreferrer"&gt;About API credentials and credential manager in API Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/api-management/api-management-howto-app-insights" rel="noopener noreferrer"&gt;Application Insights integration in API Management&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/azure/api-management/llm-emit-token-metric-policy" rel="noopener noreferrer"&gt;llm-emit-token-metric policy&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://learn.microsoft.com/entra/identity-platform/permissions-consent-overview" rel="noopener noreferrer"&gt;Permissions and consent overview - Microsoft Entra&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.cisa.gov/news-events/news/cisa-insights-solarwinds" rel="noopener noreferrer"&gt;CISA insights: SolarWinds&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://nvd.nist.gov/vuln/detail/CVE-2024-3094" rel="noopener noreferrer"&gt;NVD CVE-2024-3094 (XZ Utils backdoor)&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

</description>
      <category>oauth</category>
      <category>security</category>
      <category>zerotrust</category>
      <category>apimanagement</category>
    </item>
    <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>
  </channel>
</rss>
