<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:cc="http://cyber.law.harvard.edu/rss/creativeCommonsRssModule.html">
    <channel>
        <title><![CDATA[Stories by Rory Braybrook on Medium]]></title>
        <description><![CDATA[Stories by Rory Braybrook on Medium]]></description>
        <link>https://medium.com/@rbrayb?source=rss-6601e21c1210------2</link>
        <image>
            <url>https://cdn-images-1.medium.com/fit/c/150/150/0*qOq67kfYb5wvVi9f.jpeg</url>
            <title>Stories by Rory Braybrook on Medium</title>
            <link>https://medium.com/@rbrayb?source=rss-6601e21c1210------2</link>
        </image>
        <generator>Medium</generator>
        <lastBuildDate>Wed, 04 Nov 2026 23:23:50 GMT</lastBuildDate>
        <atom:link href="https://medium.com/@rbrayb/feed" rel="self" type="application/rss+xml"/>
        <webMaster><![CDATA[yourfriends@medium.com]]></webMaster>
        <atom:link href="http://medium.superfeedr.com" rel="hub"/>
        <item>
            <title><![CDATA[Using a .NET .http file to do an authentication code flow in Entra External ID (EEID)]]></title>
            <link>https://medium.com/the-new-control-plane/using-a-net-http-file-to-do-an-authentication-code-flow-in-entra-external-id-eeid-581e97755aaa?source=rss-6601e21c1210------2</link>
            <guid isPermaLink="false">https://medium.com/p/581e97755aaa</guid>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[entra-external-id]]></category>
            <category><![CDATA[authorization]]></category>
            <category><![CDATA[https]]></category>
            <dc:creator><![CDATA[Rory Braybrook]]></dc:creator>
            <pubDate>Mon, 02 Nov 2026 00:24:33 GMT</pubDate>
            <atom:updated>2026-11-02T00:24:56.133Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Image of normal auth request" src="https://cdn-images-1.medium.com/max/300/1*MsGVCMJHThpPTBdXh8Omew.png" /><figcaption>AI generated</figcaption></figure><p>All the .http posts are <a href="https://medium.com/@rbrayb/list/net-http-files-bab6b9147677">here</a>.</p><p>I have done a few samples that illustrate native auth in EEID, but I was asked if .http files can be used in a normal browser-based flow.</p><p>The problem is that there doesn&#39;t appear to be any way to get an .http file to “pop up” a browser-based login screen.</p><p>So you have to do this outside the .http file, e.g., by typing the request into the browser or using PowerShell.</p><p>Here is a vibe-coded PS snippet:</p><pre>&lt;#<br>.SYNOPSIS<br> Signs in to Microsoft Entra External ID (CIAM) using the standard OIDC<br> authorization code flow and prints the resulting access token so you can<br> copy/paste it (e.g. into the @accessToken variable in EEIDLogin.http).<br><br>.DESCRIPTION<br> This mirrors the normal &quot;Authenticate&quot; browser experience:<br>   1. Opens the default browser to the tenant&#39;s /authorize endpoint.<br>   2. You sign in with username + password (and MFA, if required) on the<br>   real Entra External ID hosted sign-in page.<br>   3. EEID redirects the browser to a local redirect URI<br>   (http://localhost:8400/callback) with an authorization &quot;code&quot;.<br>   4. This script runs a small local HTTP listener to catch that redirect,<br>   then exchanges the code for an access token at the /token endpoint.<br>   5. Prints the access token (and refresh token, if issued) to the console<br>   and copies the access token to your clipboard.<br><br>.NOTES<br> Update $tenantSubdomain, $tenantId, $clientId, and $clientSecret below for<br> your own tenant/app registration. The app registration&#39;s redirect URI must<br> include http://localhost:8400/callback (added as a &quot;Web&quot; or &quot;Public client&quot;<br> redirect URI, matching how the app is configured).<br>#&gt;<br><br>$tenantSubdomain = &quot;eeid-domain&quot;<br>$tenantId        = &quot;eeid-domain.onmicrosoft.com&quot;<br>$clientId        = &quot;d6...90&quot;<br>$clientSecret    = &quot;95...WB&quot;<br>$redirectUri     = &quot;http://localhost:8400/callback&quot;<br>$scope           = &quot;openid profile offline_access User.Read&quot;<br><br>$authorizeEndpoint = &quot;https://$tenantSubdomain.ciamlogin.com/$tenantId/oauth2/<br>                      v2.0/authorize&quot;<br>$tokenEndpoint     = &quot;https://$tenantSubdomain.ciamlogin.com/$tenantId/oauth2/<br>                      v2.0/token&quot;<br><br># Step 1: Build the authorize URL and start a local listener for the redirect<br>$state = [guid]::NewGuid().ToString(&quot;N&quot;)<br><br>$authorizeUrl = &quot;$authorizeEndpoint`?&quot; + (<br> @(<br>  &quot;client_id=$clientId&quot;<br>  &quot;response_type=code&quot;<br>  &quot;redirect_uri=$([uri]::EscapeDataString($redirectUri))&quot;<br>  &quot;scope=$([uri]::EscapeDataString($scope))&quot;<br>  &quot;response_mode=query&quot;<br>  &quot;state=$state&quot;<br> ) -join &quot;&amp;&quot;<br>)<br><br>$listener = New-Object System.Net.HttpListener<br>$listener.Prefixes.Add(&quot;$redirectUri/&quot;)<br>$listener.Start()<br><br>Write-Host &quot;&quot;<br>Write-Host &quot;=== Opening browser to sign in ===&quot; -ForegroundColor Cyan<br>Write-Host &quot;If a browser window doesn&#39;t open automatically, copy this URL <br>            into one:&quot;<br>Write-Host $authorizeUrl<br>Write-Host &quot;&quot;<br><br>Start-Process $authorizeUrl<br><br># Step 2: Wait for the redirect carrying the authorization code<br>$context  = $listener.GetContext()<br>$request  = $context.Request<br>$response = $context.Response<br><br>$queryParams = [System.Web.HttpUtility]::ParseQueryString($request.Url.Query)<br>$code        = $queryParams[&quot;code&quot;]<br>$returnedState = $queryParams[&quot;state&quot;]<br>$errorParam  = $queryParams[&quot;error&quot;]<br><br>$responseHtml = if ($errorParam) {<br> &quot;&lt;html&gt;&lt;body&gt;&lt;h2&gt;Sign-in failed: $errorParam&lt;/h2&gt;&lt;p&gt;$($queryParams<br>  [&#39;error_description&#39;])&lt;/p&gt;&lt;p&gt;You can close this tab.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;&quot;<br>} else {<br> &quot;&lt;html&gt;&lt;body&gt;&lt;h2&gt;Sign-in complete.&lt;/h2&gt;&lt;p&gt;You can close this tab and return <br>  to PowerShell.&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;&quot;<br>}<br><br>$buffer = [System.Text.Encoding]::UTF8.GetBytes($responseHtml)<br>$response.ContentLength64 = $buffer.Length<br>$response.OutputStream.Write($buffer, 0, $buffer.Length)<br>$response.OutputStream.Close()<br>$listener.Stop()<br><br>if ($errorParam) {<br> Write-Error &quot;Sign-in failed: $errorParam - <br>   $($queryParams[&#39;error_description&#39;])&quot;<br> exit 1<br>}<br><br>if ($returnedState -ne $state) {<br> Write-Error &quot;State mismatch - possible CSRF, aborting.&quot;<br> exit 1<br>}<br><br>if (-not $code) {<br> Write-Error &quot;No authorization code was returned.&quot;<br> exit 1<br>}<br><br># Step 3: Exchange the authorization code for an access token<br>$tokenBody = @{<br> grant_type    = &quot;authorization_code&quot;<br> client_id     = $clientId<br> client_secret = $clientSecret<br> code          = $code<br> redirect_uri  = $redirectUri<br> scope         = $scope<br>}<br><br>$tokenResponse = Invoke-RestMethod -Method Post -Uri $tokenEndpoint -Body <br>$tokenBody -ContentType &quot;application/x-www-form-urlencoded&quot;<br><br>Write-Host &quot;&quot;<br>Write-Host &quot;=== Access token (copy everything between the quotes) ===&quot; <br>  -ForegroundColor Green<br>Write-Host $tokenResponse.access_token<br>Write-Host &quot;&quot;<br><br>if ($tokenResponse.refresh_token) {<br> Write-Host &quot;=== Refresh token ===&quot; -ForegroundColor Yellow<br> Write-Host $tokenResponse.refresh_token<br> Write-Host &quot;&quot;<br>}<br><br># Also copy the access token to the clipboard for convenience, if available<br>try {<br> $tokenResponse.access_token | Set-Clipboard<br> Write-Host &quot;(Access token has also been copied to your clipboard.)&quot; <br>   -ForegroundColor DarkGray<br>}<br>catch {<br> # Set-Clipboard isn&#39;t available in all PowerShell hosts; ignore if it fails.<br>}</pre><p>Running this PS script gives:</p><pre><br>=== Opening browser to sign in ===<br>If a browser window doesn&#39;t open automatically, copy this URL into one:<br>https://eeid-domain.ciamlogin.com/eeid-domain.onmicrosoft.com/oauth2/v2.0<br>  /authorize?<br>client_id=d6...90<br>&amp;response_type=code<br>&amp;redirect_uri=http%3A%2F%2Flocalhost%3A8400%2Fcallback<br>&amp;scope=openid%20profile%20offline_access%20User.Read<br>&amp;response_mode=query<br>&amp;state=64770a6dc8ff42d386232cf1053059b7<br><br>=== Access token (copy everything between the quotes) ===<br>ey...xw<br><br>=== Refresh token ===<br>1.A...vT0<br><br>(Access token has also been copied to your clipboard.)<br></pre><p>The .http file then looks like:</p><pre>### Paste the access token you got from Get-EntraToken.ps1<br>@accessToken = ey...xw<br><br>GET https://graph.microsoft.com/v1.0/me<br>Authorization: Bearer {{accessToken}}<br>Accept: application/json<br></pre><p>This results in:</p><pre>{<br>  &quot;@odata.context&quot;: &quot;https://graph.microsoft.com/v1.0/$metadata#users/$entity&quot;,<br>  &quot;businessPhones&quot;: [],<br>  &quot;displayName&quot;: &quot;Test User&quot;,<br>  &quot;givenName&quot;: null,<br>  &quot;jobTitle&quot;: null,<br>  &quot;mail&quot;: null,<br>  &quot;mobilePhone&quot;: null,<br>  &quot;officeLocation&quot;: null,<br>  &quot;preferredLanguage&quot;: null,<br>  &quot;surname&quot;: null,<br>  &quot;userPrincipalName&quot;: &quot;d5...58@eeid-domain.onmicrosoft.com&quot;,<br>  &quot;id&quot;: &quot;d5..58&quot;<br>}</pre><p>Basically, this just doesn’t seem worth it.</p><p>It would be easier to just add the /me call to the PS script!!! 😃</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/281/0*uyuc7ek7-9Ln2ESM.png" /></figure><p>All good!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=581e97755aaa" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-new-control-plane/using-a-net-http-file-to-do-an-authentication-code-flow-in-entra-external-id-eeid-581e97755aaa">Using a .NET .http file to do an authentication code flow in Entra External ID (EEID)</a> was originally published in <a href="https://medium.com/the-new-control-plane">The new control plane</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[A comparison between Azure AD B2C and Entra External ID (EEID)]]></title>
            <link>https://medium.com/the-new-control-plane/a-comparison-between-azure-ad-b2c-and-entra-external-id-eeid-5b38140f6f12?source=rss-6601e21c1210------2</link>
            <guid isPermaLink="false">https://medium.com/p/5b38140f6f12</guid>
            <category><![CDATA[azure-ad-b2c]]></category>
            <category><![CDATA[comparison]]></category>
            <category><![CDATA[entra-external-id]]></category>
            <category><![CDATA[azure-b2c]]></category>
            <dc:creator><![CDATA[Rory Braybrook]]></dc:creator>
            <pubDate>Sun, 01 Nov 2026 19:58:53 GMT</pubDate>
            <atom:updated>2026-11-01T23:58:35.821Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Image showing comparison" src="https://cdn-images-1.medium.com/max/300/1*OGal434k3J8YJqDkjLopyQ.png" /><figcaption>AI generated</figcaption></figure><p>I did a <a href="https://medium.com/the-new-control-plane/comparing-azure-ad-b2c-and-entraexternal-id-for-customers-ciam-78a3d7c79fd1">comparison</a> two years ago, but things have changed, so I updated it.</p><p>This comparison is complicated because EEID supports both B2B and B2C functionality.</p><p>The table concentrates more on B2C, allowing a more direct comparison with Azure AD B2C.</p><p>This <a href="https://learn.microsoft.com/en-us/entra/external-id/customers/concept-supported-features-customers?WT.mc_id=AZ-MVP-5003445">Learn article</a> comparing workforce and external tenants may also be of use.</p><p>Some features require a Microsoft Entra ID P1 or P2 subscription.</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/5899485abcbb1d7bf1c71b0e2afec569/href">https://medium.com/media/5899485abcbb1d7bf1c71b0e2afec569/href</a></iframe><p>Please note any errors and omissions in the comments, and I will update the article.</p><h4>Note A</h4><p>Facebook, Google and Apple</p><h4>Note 1</h4><p>EEID only has SAML SP support. It cannot act as an inbound SAML IDP to issue SAML 2.0 assertions directly to your own line-of-business applications</p><h4>Note 2</h4><p>B2C API connectors can be invoked anywhere in the custom policy flow, and multiple times.</p><p>EEID extensions cover:</p><p>TokenIssuanceStart AttributeCollectionStart OnAttributeCollectionSubmit OnOtpSend</p><p>Each extension can only be invoked once</p><h4>Note 3</h4><p>Native passwordless phone number sign-in (where a user types in just their phone number instead of an email address or username) is a feature reserved for workforce Entra ID tenants</p><h4>Note 4</h4><p>SMS-based text message verification is supported as a second-factor MFA option</p><h4>Note 5</h4><p>Authenticator push is not supported as an MFA method</p><h4>Note 6</h4><p>EEID supports native passkey (FIDO2/WebAuthn) sign-in; B2C does not support FIDO2/WebAuthn.</p><p>Customers can use platform and browser-native passkeys such as Windows Hello, iCloud Keychain, Google Password Manager, or third-party managers like 1Password and Bitwarden</p><h4>Note 7</h4><p>Entra ID feature (authentication methods policy, e.g. TAP) usable for passwordless onboarding, including initial passkey registration, in EEID</p><h4>Note 8</h4><p>EEID inherits full Entra ID Conditional Access (device/location/risk/session controls); B2C has limited CA functionality</p><h4>Note 9</h4><p>Risk-based sign-in and user risk policies require Microsoft Entra ID Protection, which is a feature built for workforce tenants (standard Entra ID backed by Entra ID P2 licenses) to protect internal employees and B2B direct guest collaborations</p><h4>Note 10</h4><p>You can hyperlink your custom Terms of Use and Privacy Policy URLs directly within the sign-up page layout/company branding text in EEID</p><h4>Note 11</h4><p>Features like lifecycle workflows, entitlement management (access packages/approval workflows), privileged identity management (PIM), and automated access reviews are workforce capabilities and do not exist in dedicated external tenants</p><h4>Note 12</h4><p>Typically done via custom policy orchestration steps in B2C, not Graph API</p><h4>Note 13</h4><p>EEID company branding lacks full CSS/JavaScript support that B2C custom policies allow</p><h4>Note 14</h4><p>Refer <a href="https://medium.com/the-new-control-plane/using-passkeys-with-entra-external-id-eeid-21ca7f9fded8">this</a></p><h4>Note 15</h4><p>EEID tenants can mix B2B guests and external/consumer users in one directory</p><h4>Note 16</h4><p>Separate Azure AD B2C portal blade, unified Entra/EEID admin centre</p><h4>Note 17</h4><p>Microsoft has signalled Azure AD B2C is in maintenance mode, with no new features planned. Microsoft will pull the plug in 2030</p><h4>Note 18</h4><p>B2C relies on webview / browser based redirects for login, whereas native auth allows for support for inline mobile logins</p><h4>Note 19</h4><p>Limited external claim mapping. There is no way to map additional claims (outside of the standard set)</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/281/0*uyuc7ek7-9Ln2ESM.png" /></figure><p>All good!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5b38140f6f12" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-new-control-plane/a-comparison-between-azure-ad-b2c-and-entra-external-id-eeid-5b38140f6f12">A comparison between Azure AD B2C and Entra External ID (EEID)</a> was originally published in <a href="https://medium.com/the-new-control-plane">The new control plane</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to create a .http file from scratch]]></title>
            <link>https://medium.com/the-new-control-plane/how-to-create-a-http-file-from-scratch-5950d7a27c8d?source=rss-6601e21c1210------2</link>
            <guid isPermaLink="false">https://medium.com/p/5950d7a27c8d</guid>
            <category><![CDATA[postman]]></category>
            <category><![CDATA[https]]></category>
            <dc:creator><![CDATA[Rory Braybrook]]></dc:creator>
            <pubDate>Sat, 31 Oct 2026 19:57:55 GMT</pubDate>
            <atom:updated>2026-10-31T20:00:39.815Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Image of http file" src="https://cdn-images-1.medium.com/max/300/1*HyCJcMNCMH2GShFFE-RH4w.png" /><figcaption>AI generated</figcaption></figure><p>All the .http posts are <a href="https://medium.com/@rbrayb/list/net-http-files-bab6b9147677">here</a>.</p><p>An “.http” file is just a plain text file with the “.http” extension. It uses a simple syntax to define one or more HTTP requests that you can run directly from your editor.</p><h4>Syntax</h4><ul><li>Variables (optional, reusable values)</li></ul><p>Define variables at the top of the file and reference them later with “{{name}}”:</p><pre>@baseUrl = https://graph.microsoft.com/v1.0<br>@token = eyJ…</pre><ul><li>A request block</li></ul><p>A request starts with an HTTP method and URL, followed by optional headers, a blank line, and then an optional body:</p><pre>GET {{baseUrl}}/me<br>Authorization: Bearer {{token}}<br>Accept: application/json</pre><ul><li>Separating multiple requests</li></ul><p>Use a line containing “###” to separate independent requests within the same file:</p><pre>### Get my profile<br>GET {{baseUrl}}/me<br>Authorization: Bearer {{token}}<br>### Create something<br>POST {{baseUrl}}/me/sendMail<br>Authorization: Bearer {{token}}<br>Content-Type: application/json<br>{<br> &quot;message&quot;: {<br> &quot;subject&quot;: &quot;Test&quot;,<br> &quot;body&quot;: { &quot;contentType&quot;: &quot;Text&quot;, &quot;content&quot;: &quot;Hello&quot; },<br> &quot;toRecipients&quot;: [{ &quot;emailAddress&quot;: { &quot;address&quot;: &quot;someone@example.com&quot; } }]<br> }<br>}</pre><h4>Key syntax rules</h4><p>- “# comment” or “// comment” — comment lines<br>- “<a href="http://twitter.com/name">@name</a> = value” — define a variable reference with “{{name}}”<br>- “###” on its own line — separates independent requests in the same file<br>- Method + URL on one line (“GET”, “POST”, “PUT”, “DELETE”, etc.)<br>- Headers go on the lines right after, one per line (“Header-Name: value”)<br>- A blank line separates headers from the request body (if any)</p><h4>Running the file</h4><ul><li>For VS Code, install the “REST Client” extension (humao.rest-client), then click the “Send Request” CodeLens link that appears above each request, or press “Ctrl+Alt+R” with the cursor inside a request block</li><li>For Visual Studio 2026, “.http&quot; files are supported natively via the built-in<br> HTTP editor — just open the file and click the run/send icon next to each request.</li></ul><p>No special tooling is required to create the file — only to execute the requests inside it.</p><h4>How does this compile?</h4><p>An “.http” file is not source code — it isn’t parsed into an assembly, isn’t type-checked, and has no build step. It’s a plain-text description of raw HTTP requests, so a compiler has nothing to produce.</p><ul><li>No syntax requires compilation — the “@variable”, “###”, method/URL, headers, and body are just text that the editor’s HTTP client (REST Client in VS Code, or the built-in HTTP editor in Visual Studio) reads and sends over the wire at the moment you click “Send Request”</li><li>No project reference or assembly output — unlike “.cs”/”.vb” files, “.http” files aren’t included in a project’s build (“.csproj”) compilation pass, so adding, editing, or saving one never triggers (or breaks) a build</li><li>Execution happens live— each time you click “Send Request”, the tool substitutes variables (“{{baseUrl}}”, “{{token}}”, etc.) and fires the literal HTTP request immediately. There’s no intermediate step that transforms the file into something else first</li><li>Instant edit-and-retry loop — because nothing is compiled, you can change the URL, header, or body text and resend the request right away without waiting for a build or restarting any process</li></ul><p>In short, “.http” files are interpreted on the spot by your editor’s HTTP client, the same way a “curl” command or Postman request is “run” rather than “built”</p><p>Here’s an example:</p><pre># How to get an access token from Microsoft Graph Explorer:<br># 1. Go to https://developer.microsoft.com/graph/graph-explorer<br># 2. Sign in (top-left &quot;Sign in&quot; button) with the account you want to call <br>#    Graph as.<br># 3. Consent to the permissions you need (e.g. User.Read) when prompted.<br># 4. Run any query (e.g. GET /me) by clicking &quot;Run query&quot;.<br># 5. Copy the token: Check the response panel&#39;s &quot;Access token&quot; tab if your <br>#    version of Graph Explorer shows one.<br># 6. Paste the copied token below as the value of the &quot;token&quot; variable <br>#    (replacing the old one).<br># NOTE: Tokens expire after ~1 hour, so you&#39;ll need to repeat this when it <br>#       expires.<br><br># How to send this request from VS Code:<br># 1. Install the &quot;REST Client&quot; extension (humao.rest-client) from the <br>#    Extensions marketplace.<br># 2. Open this .http file in VS Code.<br># 3. Click the &quot;Send Request&quot; link that appears above the GET line (CodeLens),<br>#    or place your cursor in the request block and press Ctrl+Alt+R.<br># 4. The response opens in a side pane in VS Code.<br># 5. If you update the token or base URL variables, just re-click <br>#    &quot;Send Request&quot; to pick up the change.<br><br>@baseUrl = https://graph.microsoft.com/v1.0<br>@token = ey...sA<br>### Get the signed-in user&#39;s profile<br>GET {{baseUrl}}/me<br>Authorization: Bearer {{token}}<br>Accept: application/json<br><br>### Get the signed-in user&#39;s direct reports<br>GET {{baseUrl}}/me/directReports<br>Authorization: Bearer {{token}}<br>Accept: application/json<br><br>### Get more details about the signed-in user (select additional properties)<br>GET {{baseUrl}}/me?$select=displayName,givenName,surname,mail,<br>    userPrincipalName,jobTitle,department,officeLocation,mobilePhone,<br>    businessPhones,preferredLanguage<br>Authorization: Bearer {{token}}<br>Accept: application/json<br></pre><p>Sadly, there doesn&#39;t seem to be any syntax to automatically pop up a login screen 😢 to get the access token.</p><p>The easiest way to get an access token is to use Graph Explorer, log in with the user, run a request and then copy the access token into the “@token = ey…sA” line in the script above.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ZDaGnbEOQZypQQ7KamQB1A.png" /></figure><p>VS automatically adds the “Send request” commands. That’s why you don’t see them in the samples I’ve included.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*ebqsph2zT6kPzoyuKNG2VQ.png" /></figure><p>Running, e.g. the first command shows:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/1024/1*N119XBKVl4xq9dfAd15AcQ.png" /></figure><p>A common pattern is to use some variable returned in the previous response in the next request.</p><p>As an example:</p><pre>### Get more details about the signed-in user (select additional properties)<br># @name meDetails<br>GET {{baseUrl}}/me?$select=displayName,givenName,surname,mail,<br>    userPrincipalName,jobTitle,department,officeLocation,mobilePhone,<br>    businessPhones,preferredLanguage<br>Authorization: Bearer {{token}}<br>Accept: application/json<br><br>### Look up the same user by userPrincipalName (uses the value from the <br>    previous response)<br># NOTE: Send the &quot;meDetails&quot; request above at least once before sending this <br>#       one, so Visual Studio has a cached response to pull <br>#       userPrincipalName from<br>GET {{baseUrl}}/users/{{meDetails.response.body.$.userPrincipalName}}<br>Authorization: Bearer {{token}}<br>Accept: application/json</pre><p>The first request (which we reference as “@name meDetails”) returns userPrincipalName, which we use in the second request.</p><p><strong>Note</strong>: I used AI to write the VS Code parts, as I don’t use VS Code very often.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/281/0*uyuc7ek7-9Ln2ESM.png" /></figure><p>All good!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=5950d7a27c8d" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-new-control-plane/how-to-create-a-http-file-from-scratch-5950d7a27c8d">How to create a .http file from scratch</a> was originally published in <a href="https://medium.com/the-new-control-plane">The new control plane</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using passkeys with Entra External ID (EEID)]]></title>
            <link>https://medium.com/the-new-control-plane/using-passkeys-with-entra-external-id-eeid-21ca7f9fded8?source=rss-6601e21c1210------2</link>
            <guid isPermaLink="false">https://medium.com/p/21ca7f9fded8</guid>
            <category><![CDATA[passkey]]></category>
            <category><![CDATA[authentication]]></category>
            <category><![CDATA[entra-external-id]]></category>
            <category><![CDATA[1password]]></category>
            <dc:creator><![CDATA[Rory Braybrook]]></dc:creator>
            <pubDate>Mon, 26 Oct 2026 22:34:36 GMT</pubDate>
            <atom:updated>2026-10-26T22:34:58.018Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Image of passkey authentication" src="https://cdn-images-1.medium.com/max/300/1*sA5Lyn7yoa6QNA5ejbBF2w.png" /><figcaption>AI generated</figcaption></figure><p>The docs are <a href="https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-sign-in-with-passkey?WT.mc_id=AZ-MVP-5003445">here</a>. The repo is <a href="https://github.com/Azure-Samples/ms-identity-ciam-native-javascript-samples/tree/main/passkey-sample">here</a>.</p><p>In EEID, ensure FIDO2 is enabled in Authentication methods.</p><figure><img alt="Image showing Passkey (FIDO2) enabled" src="https://cdn-images-1.medium.com/max/962/1*PQ4XUqeDvQ-JpW3FvGJ43w.png" /></figure><p>Click on “Passkey”:</p><figure><img alt="Image showing using passket default profile" src="https://cdn-images-1.medium.com/max/921/1*bRAgtpVgbKaR9Uk24OJWdA.png" /></figure><p>Click on “Configure”:</p><figure><img alt="Image showing no attestation or key restirctions" src="https://cdn-images-1.medium.com/max/1024/1*E_8cr1YJFf-2pMie8hu4ag.png" /></figure><p>I use 1Password, so I need “Synced” enabled.</p><p>There are two modes:</p><ul><li><strong>“Passwordless sign-in.</strong> The user enters their email or username. If a passkey is available, they’re prompted to sign in with face, fingerprint, PIN, or a security key. The passkey completes first-factor authentication and also satisfies multifactor authentication (MFA) requirements.</li><li><strong>Passkey for MFA.</strong> The user signs in with their email or username and password. If a passkey is registered, they complete MFA with the passkey — a phishing-resistant alternative to traditional verification methods”.</li></ul><p>I just used the former.</p><p>For the latter, you need to create a CA policy.</p><p>You also need:</p><ul><li>“An account with at least the “Authentication Policy Administrator” role to configure passkey policies.</li><li>A <a href="https://learn.microsoft.com/en-us/entra/external-id/customers/how-to-custom-url-domain?WT.mc_id=AZ-MVP-5003445">custom URL domain</a> configured for your tenant. Passkeys are registered against the custom URL as the relying party”.</li></ul><p><strong>Note</strong>: Only “email + password” and “username + password” local accounts can register a passkey.</p><p>Some points to note from the docs:</p><h4>Is Microsoft Authenticator supported for passkeys?</h4><p>No. The Microsoft Authenticator app doesn’t currently support registering passkeys.</p><h4>Can customers use a passkey on a different device from where they registered it?</h4><p>Yes — synced passkeys are automatically available on all linked devices.</p><h4>Can I enforce passkeys using Conditional Access authentication strengths?</h4><p>No. CA works in EEID, but EEID tenants don’t currently support Conditional Access authentication strengths.</p><h4>Can I use passkeys with email one-time passcode or social IdP users?</h4><p>Not yet. Only email + password local account users can use passkeys currently.</p><h4>Is there a cost for passkey authentication?</h4><p>No. Passkeys are included in all Microsoft Entra External ID pricing tiers at no additional cost, unlike SMS-based MFA, which requires a linked subscription.</p><h4>Are passkeys supported in Azure AD B2C?</h4><p>No. Passkeys for customer-facing apps are available only in Microsoft Entra External ID (external tenants). If you’re on B2C, consider planning your migration to External ID.</p><h4>Are there low-privilege APIs for building a credential management experience?</h4><p>No. Use the FIDO2 provisioning APIs to build your credential management experience.</p><h4>Are passkeys supported in mobile native authentication flows?</h4><p>No. Passkeys aren’t currently supported through native authentication APIs.</p><h4>Is there an out-of-the-box passkey registration experience?</h4><p>No. Microsoft doesn’t currently provide a built-in passkey registration experience for external tenants. Build a credential management experience in your application by using the FIDO2 provisioning APIs.</p><p>Which is a nice segue into the repo!</p><p>The repo shows the out-of-the-box experience. It’s just a sample application and should not be deployed to production.</p><p>Follow the steps for:</p><ul><li>Edit the hosts file</li><li>Generate an SSL certificate for the proper domain</li><li>Use a test tenant and single-tenant app registration</li><li>Register your redirect URI in the EEID portal</li></ul><h4><em>Application Configuration</em></h4><p>My authConfig.js looks like:</p><pre>auth: {<br>    clientId: &#39;95...53&#39;, // This is the ONLY mandatory field that you need to <br>      // supply.<br>    authority: &#39;https://ciam-tenant.ciamlogin.com/&#39;, // Replace the <br>      // placeholder with your tenant subdomain <br>    redirectUri: &#39;/&#39;,<br>    postLogoutRedirectUri: &#39;/&#39;,<br>    navigateToLoginRequestUrl: false,<br>},</pre><p>and my env file looks like:</p><pre># Local dev hostname — must match the auth.&lt;ciam-tenant&gt;.ciamlogin.com <br># subdomain you added to your hosts file<br># Example: auth.ciam-tenant.ciamlogin.com<br>VITE_HOST=auth.ciam-tenant.ciamlogin.com<br>VITE_PORT=3000<br><br># SSL certificate filenames (generated locally — see README &quot;Generate SSL <br># Certificate&quot;)<br>VITE_SSL_CERT=auth-cert.pem<br>VITE_SSL_KEY=auth-key.pem<br><br># Client secret from your Entra app registration (do NOT commit your <br># real .env)<br>VITE_APP_SECRET=qH...au</pre><p><strong>Note</strong>: You need a client secret from your app registration. This step is missing in the docs.</p><p><strong>Note</strong>: The docs list “a custom URL domain configured for your tenant” as a prerequisite, but this is only required if you want passkeys registered against your own branded domain (e.g., “login.yourcompany.com”) in production.</p><p>For local testing, the sample works fine using the tenant’s default “ciam-tenant.ciamlogin.com” subdomain as the relying party — no custom domain purchase/verification needed.</p><p>Then:</p><ul><li>Install dependencies</li></ul><p><em>npm install</em></p><ul><li>Start CORS proxy server</li></ul><p><em>npm run cors</em></p><p>This starts the CORS proxy on http://localhost:3001.</p><p>* Start sample app</p><p><em>npm start</em></p><ul><li>Open your browser and navigate to:</li></ul><p><em>https://auth.ciam-tenant.ciamlogin.com:3000</em></p><p>You see:</p><figure><img alt="Image showing passkey turorial login screen" src="https://cdn-images-1.medium.com/max/1024/1*0WzBzrdVotl6H4MuKsjIhg.png" /></figure><p>Click “Sign in”.</p><p>Sign in as normal. You will see:</p><figure><img alt="Image showing passkey management screen" src="https://cdn-images-1.medium.com/max/1024/1*74Fxj3CdmI3KlFEPlkyb2A.png" /></figure><p>Click “Add Passkey”.</p><p>1Password shows:</p><figure><img alt="Image showing 1Password “Save passkey” screen" src="https://cdn-images-1.medium.com/max/538/1*BxuhrdRgUPcue7I-TKyWQw.png" /></figure><p>Save the passkey and then sign out.</p><p>Sign in again, and you see:</p><figure><img alt="Image showing login screen with passkey option" src="https://cdn-images-1.medium.com/max/623/1*SmX_x6AlKikZttSP9LyipA.png" /></figure><p>Click the saved passkey, and you are automatically signed in.</p><p>Neat 😃</p><h4>Issue</h4><p>While working through this sample, I hit a confusing error.</p><p>Signing in with a 1Password-synced passkey consistently failed with:</p><pre>We couldn&#39;t sign you in. Your sign-in was successful but this passkey does <br>not meet the criteria set by your admin. Try using another authentication <br>method.</pre><p>Checking the EEID sign-in logs showed a more specific failure:</p><pre>Sign-in error code: 135016 Failure reason: FIDO sign-in is disabled via <br>policy.</pre><p>I worked through the usual suspects:</p><pre>GET https://graph.microsoft.com/beta/policies/authenticationMethodsPolicy/<br>authenticationMethodConfigurations/Fido2</pre><p>showed:</p><ul><li>state: enabled</li><li>attestation enforcement off</li><li>no key restrictions,</li><li>passkeyTypes: “deviceBound, synced” both allowed</li><li>“all_users” included</li></ul><p>I thought it could be an authentication strength issue, but “Conditional Access authentication strengths” don&#39;t apply to EEID.</p><p>However, running the exact same sign-in flow in Chrome instead of Firefox fixed the problem!</p><p>This is most likely a Firefox/1Password WebAuthn compatibility gap rather than an Entra configuration issue.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/281/0*uyuc7ek7-9Ln2ESM.png" /></figure><p>All good!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=21ca7f9fded8" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-new-control-plane/using-passkeys-with-entra-external-id-eeid-21ca7f9fded8">Using passkeys with Entra External ID (EEID)</a> was originally published in <a href="https://medium.com/the-new-control-plane">The new control plane</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using Verified ID with Entra External ID (EEID)]]></title>
            <link>https://medium.com/the-new-control-plane/using-verified-id-with-entra-external-id-eeid-547c15be1097?source=rss-6601e21c1210------2</link>
            <guid isPermaLink="false">https://medium.com/p/547c15be1097</guid>
            <category><![CDATA[entra-external-id]]></category>
            <category><![CDATA[verified-credential]]></category>
            <category><![CDATA[verified-identity]]></category>
            <dc:creator><![CDATA[Rory Braybrook]]></dc:creator>
            <pubDate>Tue, 20 Oct 2026 22:33:04 GMT</pubDate>
            <atom:updated>2026-10-21T03:01:08.460Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Image showing verifiable ID" src="https://cdn-images-1.medium.com/max/300/1*H7OxGAeQ99Ls1z6dKDpXQw.png" /><figcaption>AI generated</figcaption></figure><p>The docs are <a href="https://learn.microsoft.com/en-us/entra/external-id/customers/verified-id-setup-issuance-presentation">here</a>. The repo is<a href="https://github.com/Azure-Samples/active-directory-verifiable-credentials-dotnet/tree/main"> here</a>.</p><p>You can find my previous VI posts <a href="https://authory.com/RoryBraybrook?collection=c1845d8419b2a435ca1cbe727b6c953a0&amp;text=%22verified%20id%22">here</a>.</p><p>You’ll find the VI tab in the EEID portal here:</p><figure><img alt="Image showing VI tab in portal" src="https://cdn-images-1.medium.com/max/339/1*l7ssZaw1VIKfa85AtYGEvw.png" /></figure><p>For the quick start, you need a <a href="https://learn.microsoft.com/en-us/entra/fundamentals/add-custom-domain?WT.mc_id=AZ-MVP-5003445">custom domain</a>.</p><p><strong>Note</strong>: Without a registered custom domain, there isn’t a supported Verified ID setup path for an external tenant.</p><figure><img alt="Image showing you need a custom domain" src="https://cdn-images-1.medium.com/max/1024/1*Np4DHFu7g_-az250HvVK3g.png" /></figure><figure><img alt="Image of “Get started” button" src="https://cdn-images-1.medium.com/max/1024/1*F1ammHww-QvYYw02ARHxWg.png" /></figure><p>Click the “Get started” button in the VI tab to complete setup in less than a minute.</p><p>The overview page shows:</p><figure><img alt="Image showing “Get new credential” — “Try it now”" src="https://cdn-images-1.medium.com/max/1024/1*2KRObaRFxQ0MQ_62zrxoMw.png" /></figure><p>Click “Try it now”.</p><p>You’ll be redirected to your “myaccount” page, and on that page, you will see:</p><figure><img alt="Image showing “Avaialable VI”" src="https://cdn-images-1.medium.com/max/524/1*UhdFfHd_bqwDQGATATHStA.png" /></figure><p>Clicking that will show:</p><figure><img alt="Image showing “Get ID”" src="https://cdn-images-1.medium.com/max/912/1*YDb7fsCIJo166UfFOT10VA.png" /></figure><p>Click “Get ID”.</p><p>You’ll see:</p><figure><img alt="Image showing “Scan QR code”" src="https://cdn-images-1.medium.com/max/717/1*1ojIPApoSBbEpjLK1u0dog.png" /></figure><p>On your mobile (assuming you already have Microsoft Authenticator), open the Authenticator app and tap “Verified IDs” in the bottom-right corner.</p><p>Click the “Scan QR” icon and scan the above QR code.</p><p>Then add the VI.</p><p>You will now see a “Verified Employee” VI in the list.</p><p>Back on the EEID VI overview page, click “Try it now” under “Use your new credential”.</p><p>You will see:</p><figure><img alt="Image showing Proseware “Access discounts”" src="https://cdn-images-1.medium.com/max/1024/1*6Ci5wdLUg8orMrMwtWnkQw.png" /></figure><p>Click “Access discounts”. You see:</p><figure><img alt="Image showing sign in — “Verify my Employee Credential”" src="https://cdn-images-1.medium.com/max/584/1*lYuPYVqp4fZkYjUUnHk3Ng.png" /></figure><p>Click “Verify my Employee Credential”.</p><figure><img alt="Image showing scan QR code" src="https://cdn-images-1.medium.com/max/579/1*SYZVCiRP8aNxyHPu0BPTnA.png" /></figure><p>If you have multiple, select one.</p><p>Scan the QR code in the “Verified IDs” section as above.</p><p>Then you will see:</p><figure><img alt="Image showing Proseware — “Your discount is applied”" src="https://cdn-images-1.medium.com/max/1024/1*dbIJ6SV2C9B7yNfqAN8nmQ.png" /></figure><p>and your discount is applied.</p><p>Quick and neat!</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/281/0*uyuc7ek7-9Ln2ESM.png" /></figure><p>All good!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=547c15be1097" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-new-control-plane/using-verified-id-with-entra-external-id-eeid-547c15be1097">Using Verified ID with Entra External ID (EEID)</a> was originally published in <a href="https://medium.com/the-new-control-plane">The new control plane</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[How to run a .http file]]></title>
            <link>https://medium.com/the-new-control-plane/how-to-run-a-http-file-a89bfd87b7a5?source=rss-6601e21c1210------2</link>
            <guid isPermaLink="false">https://medium.com/p/a89bfd87b7a5</guid>
            <category><![CDATA[visual-studio]]></category>
            <category><![CDATA[https]]></category>
            <dc:creator><![CDATA[Rory Braybrook]]></dc:creator>
            <pubDate>Fri, 04 Sep 2026 23:57:06 GMT</pubDate>
            <atom:updated>2026-10-31T20:02:00.024Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Image of running a .http file" src="https://cdn-images-1.medium.com/max/300/1*_tHH95t29SjJ-chGG4Wsyw.png" /><figcaption>AI generated</figcaption></figure><p>All the .http posts are <a href="https://medium.com/@rbrayb/list/net-http-files-bab6b9147677">here</a>.</p><p>Several people have asked how to run the .http file I have been using to illustrate native auth flows in Entra External ID.</p><p>This assumes you are using Visual Studio.</p><ul><li>Open the .http file in Visual Studio (double-click it, or File &gt; Open &gt; File)</li><li>No project/solution is required — .http files can be opened standalone</li><li>Each request block has a “Send Request” link</li><li>There is also a “Debug” link, which additionally launches/attaches the debugger for requests targeting your own local project — not applicable here since these requests target an Entra External ID tenant</li><li>Click “Send Request” to execute a single request. The response (status,<br> headers, body) opens in a separate tab/pane</li><li>Variables are defined with “<a href="http://twitter.com/name">@name</a> = value” and referenced with<br> “{{name}}”</li><li>Run the requests <strong>in sequence</strong>.</li><li>Ensure you have set the variables:</li></ul><pre>@tenant = eeid tenant name e.g. contoso<br>@tenantDomain = contoso.onmicrosoft.com<br>@clientId = dd...b9<br>@username = some-user@company.co.nz<br>@password = some-password</pre><p>All good!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=a89bfd87b7a5" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-new-control-plane/how-to-run-a-http-file-a89bfd87b7a5">How to run a .http file</a> was originally published in <a href="https://medium.com/the-new-control-plane">The new control plane</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using the .NET .http file to test native auth with SSPR in Entra External ID (EEID)]]></title>
            <link>https://medium.com/the-new-control-plane/using-the-net-http-file-to-test-native-auth-with-sspr-in-entra-external-id-eeid-4a1e87a5e883?source=rss-6601e21c1210------2</link>
            <guid isPermaLink="false">https://medium.com/p/4a1e87a5e883</guid>
            <category><![CDATA[entra-external-id]]></category>
            <category><![CDATA[native-authentication]]></category>
            <category><![CDATA[https]]></category>
            <category><![CDATA[dotnet]]></category>
            <dc:creator><![CDATA[Rory Braybrook]]></dc:creator>
            <pubDate>Tue, 01 Sep 2026 04:05:52 GMT</pubDate>
            <atom:updated>2026-10-31T20:02:33.845Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Image of native auth with SSPR" src="https://cdn-images-1.medium.com/max/300/1*gP01kd_gc-hQvfSATU3Ubg.png" /><figcaption>AI generated</figcaption></figure><p>All the .http posts are <a href="https://medium.com/@rbrayb/list/net-http-files-bab6b9147677">here</a>.</p><p>SSPR = Self-service password reset.</p><p>Read this <a href="https://medium.com/the-new-control-plane/using-the-net-http-file-to-test-native-auth-in-entra-external-id-eeid-fa2aeea1a8f2">post</a> and this <a href="https://medium.com/the-new-control-plane/using-the-net-http-file-to-test-native-auth-with-email-otp-in-entra-external-id-eeid-308f11f73c6f">post</a> first to get the background.</p><p>For this example, you need a user flow for email/<strong>password</strong>.</p><p>The .http file below is a <strong>continuation</strong> of the one in the post above.</p><p>This flow is more complicated because an OTP is sent to the email address, and a new password has to be entered, so you have to run the script in three parts:</p><ul><li>Run the start sign-in request</li><li>Run the challenge request</li><li>Get the OTP code sent to the email address</li><li>Update the .http file for the OTP via @ssprOtpCode = some-code and save it</li><li>Run the continue request</li><li>Enter a new password in @newPassword (or do this beforehand)</li><li>Run the submit request</li><li>Run the poll_completion request. This is optional.</li></ul><p>“Since updating of the user’s configuration with the new password incurs some delay, the app can use the /resetpassword/v1.0/poll_completion endpoint to poll Microsoft Entra for password reset status. The minimum amount of time in seconds that the app should wait between polling requests is returned from the /resetpassword/v1.0/submit endpoint in the poll_interval parameter.”</p><p>The submit request returns e.g.:</p><pre>{<br>  &quot;poll_interval&quot;: 2<br>}</pre><p>The poll_completion request should return:</p><pre>{<br>  &quot;status&quot;: &quot;succeeded&quot;<br>}</pre><p>If the status is something else and since “.http” files can’t loop automatically, just wait at least “poll_interval” seconds before manually clicking “Send request” on poll_completion again.</p><p>Check the request succeeded.</p><p>The script is:</p><pre>###<br><br>### Native Auth Password SSPR<br><br>### Start sign up (SSPR)<br># @name ssprStart<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/resetpassword/v1.0/start<br>Content-Type: application/x-www-form-urlencoded<br>Host: {{tenant}}.ciamlogin.com<br><br>client_id={{clientId}}&amp;challenge_type=oob redirect&amp;username={{username}}<br><br>###<br><br>### Challenge (SSPR) - triggers the email one-time passcode to be sent<br># @name ssprChallenge<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/resetpassword/v1.0/<br>  challenge<br>Content-Type: application/x-www-form-urlencoded<br><br>client_id={{clientId}}&amp;challenge_type=oob redirect&amp;continuation_token=<br>  {{ssprStart.response.body.$.continuation_token}}<br><br>###<br><br>### &lt;&lt;&lt; MANUAL STEP &gt;&gt;&gt;<br>### Check email for the one-time passcode, then paste it below before running <br>### Continue<br>@ssprOtpCode = 29563617<br><br>### Continue (SSPR) - redeem the OTP<br># @name ssprContinue<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/resetpassword/v1.0/<br>  continue<br>Content-Type: application/x-www-form-urlencoded<br><br>client_id={{clientId}}&amp;continuation_token={{ssprChallenge.response.body.$.<br>  continuation_token}}&amp;grant_type=oob&amp;oob={{ssprOtpCode}}<br><br>###<br><br>### &lt;&lt;&lt; MANUAL STEP &gt;&gt;&gt;<br>### Set the new password before running Submit<br>@newPassword = some-password<br><br>### Submit (SSPR) - submit the new password<br># @name ssprSubmit<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/resetpassword/v1.0/<br>  submit<br>Content-Type: application/x-www-form-urlencoded<br><br>client_id={{clientId}}&amp;continuation_token={{ssprContinue.response.body.$.<br>  continuation_token}}&amp;new_password={{newPassword}}<br><br>###<br><br>### &lt;&lt;&lt; OPTIONAL STEP &gt;&gt;&gt;<br>### Poll Completion (SSPR)<br># @name ssprPollCompletion<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/resetpassword/v1.0/<br>  poll_completion<br>Content-Type: application/x-www-form-urlencoded<br><br>client_id={{clientId}}&amp;continuation_token={{ssprSubmit.response.body.$.<br>  continuation_token}}<br><br>###</pre><p><strong>Note</strong>: The URL and body lines are wrapped here for readability only.</p><p>An example of the email is:</p><pre>Account verification code<br>To access eeid-tenant&#39;s apps and resources, please use the code below for <br>account verification. The code will only work for 30 minutes.<br>Account verification code:<br>29184860</pre><p>There is no token endpoint in the flow, so no JWTs are returned.</p><p>Refer to “<a href="https://medium.com/the-new-control-plane/how-to-run-a-http-file-a89bfd87b7a5">How to run a .http file</a>”.</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/281/0*uyuc7ek7-9Ln2ESM.png" /></figure><p>All good!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=4a1e87a5e883" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-new-control-plane/using-the-net-http-file-to-test-native-auth-with-sspr-in-entra-external-id-eeid-4a1e87a5e883">Using the .NET .http file to test native auth with SSPR in Entra External ID (EEID)</a> was originally published in <a href="https://medium.com/the-new-control-plane">The new control plane</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using the .NET .http file to test native auth with email/OTP in Entra External ID (EEID)]]></title>
            <link>https://medium.com/the-new-control-plane/using-the-net-http-file-to-test-native-auth-with-email-otp-in-entra-external-id-eeid-308f11f73c6f?source=rss-6601e21c1210------2</link>
            <guid isPermaLink="false">https://medium.com/p/308f11f73c6f</guid>
            <category><![CDATA[native-authentication]]></category>
            <category><![CDATA[entra-external-id]]></category>
            <category><![CDATA[dotnet]]></category>
            <category><![CDATA[https]]></category>
            <dc:creator><![CDATA[Rory Braybrook]]></dc:creator>
            <pubDate>Mon, 31 Aug 2026 21:40:47 GMT</pubDate>
            <atom:updated>2026-10-31T20:03:24.093Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Image of native auth with email/OTP" src="https://cdn-images-1.medium.com/max/300/1*GkoDgxy1dasZaw3HozD1nA.png" /><figcaption>AI generated</figcaption></figure><p>All the .http posts are <a href="https://medium.com/@rbrayb/list/net-http-files-bab6b9147677">here</a>.</p><p>Read this <a href="https://medium.com/the-new-control-plane/using-the-net-http-file-to-test-native-auth-in-entra-external-id-eeid-fa2aeea1a8f2">post</a> first to get the background.</p><p>For this example, you need a user flow for email/OTP.</p><p>The .http file below is a <strong>continuation</strong> of the one in the post above.</p><p>This flow is more complicated because an OTP is sent to the email address, so you have to run the script in two parts:</p><ul><li>Run the sign-in and challenge requests</li><li>Get the OTP code sent to the email address</li><li>Update the .http file for the OTP via @otpCode = some-code and save it</li><li>Run the token request</li></ul><pre>### Native Auth OTP<br><br>@otpClientId = 61...9b<br>@otpUsername = user@gmail.com<br><br>### Start sign in (OTP)<br># @name otpStartSignUp<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/oauth2/v2.0/initiate<br>Content-Type: application/x-www-form-urlencoded<br>Host: {{tenant}}.ciamlogin.com<br><br>client_id={{otpClientId}}&amp;challenge_type=oob redirect&amp;username={{otpUsername}}<br><br>###<br><br>### Challenge (OTP) - triggers the email one-time passcode to be sent<br># @name otpChallenge<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/oauth2/v2.0/challenge<br>Content-Type: application/x-www-form-urlencoded<br><br>client_id={{otpClientId}}&amp;challenge_type=oob redirect&amp;continuation_token=<br>  {{otpStartSignUp.response.body.$.continuation_token}}<br><br>###<br><br>### &lt;&lt;&lt; MANUAL STEP &gt;&gt;&gt;<br>### Check email for the one-time passcode, then paste it below before running <br>### the Token request<br>@otpCode = 29184860<br><br>### Token (OTP)<br># @name otpToken<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/oauth2/v2.0/token<br>Content-Type: application/x-www-form-urlencoded<br><br>client_id={{otpClientId}}&amp;continuation_token={{otpChallenge.response.body.$.<br>  continuation_token}}&amp;grant_type=oob&amp;scope=openid offline_access<br>  &amp;oob={{otpCode}}<br><br>###</pre><p><strong>Note</strong>: The body lines are wrapped here for readability only.</p><p>An example of the email is:</p><pre>Account verification code<br>To access eeid-tenant&#39;s apps and resources, please use the code below for <br>account verification. The code will only work for 30 minutes.<br>Account verification code:<br>29184860</pre><p>The result is:</p><pre>{     <br>  &quot;token_type&quot;: &quot;Bearer&quot;,<br>  &quot;scope&quot;: &quot;openid profile email 00000003-0000-0000-c000-00...00/User.Read&quot;,<br>  &quot;expires_in&quot;: 4539,<br>  &quot;ext_expires_in&quot;: 4539,<br>  &quot;access_token&quot;: &quot;ey...7w&quot;,<br>  &quot;refresh_token&quot;: &quot;1.A...v5M&quot;,<br>  &quot;id_token&quot;: &quot;ey...mA&quot;<br>}</pre><p>A full set of JWTs is returned.</p><p>Refer to “<a href="https://medium.com/the-new-control-plane/how-to-run-a-http-file-a89bfd87b7a5">How to run a .http file</a>”.</p><figure><a href="https://www.buymeacoffee.com/rbrayb"><img alt="" src="https://cdn-images-1.medium.com/max/281/1*V_86loHl_gBsQqg83gtCLQ.png" /></a></figure><p>All good!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=308f11f73c6f" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-new-control-plane/using-the-net-http-file-to-test-native-auth-with-email-otp-in-entra-external-id-eeid-308f11f73c6f">Using the .NET .http file to test native auth with email/OTP in Entra External ID (EEID)</a> was originally published in <a href="https://medium.com/the-new-control-plane">The new control plane</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using the .NET .http file to test native auth in Entra External ID (EEID)]]></title>
            <link>https://medium.com/the-new-control-plane/using-the-net-http-file-to-test-native-auth-in-entra-external-id-eeid-fa2aeea1a8f2?source=rss-6601e21c1210------2</link>
            <guid isPermaLink="false">https://medium.com/p/fa2aeea1a8f2</guid>
            <category><![CDATA[https]]></category>
            <category><![CDATA[postman]]></category>
            <category><![CDATA[entra-external-id]]></category>
            <category><![CDATA[native-authentication]]></category>
            <category><![CDATA[dotnet]]></category>
            <dc:creator><![CDATA[Rory Braybrook]]></dc:creator>
            <pubDate>Mon, 31 Aug 2026 03:09:15 GMT</pubDate>
            <atom:updated>2026-10-31T20:03:50.737Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Image of native auth via .http" src="https://cdn-images-1.medium.com/max/300/1*Wa8KGYWtfgsH4vWQASoKiA.png" /><figcaption>AI generated</figcaption></figure><p>All the .http posts are <a href="https://medium.com/@rbrayb/list/net-http-files-bab6b9147677">here</a>.</p><p>The .http docs are <a href="https://learn.microsoft.com/en-us/aspnet/core/test/http-files?view=aspnetcore-10.0?WT.mc_id=AZ-MVP-5003445">here</a>.</p><p>The native auth docs are <a href="https://learn.microsoft.com/en-us/entra/identity-platform/reference-native-authentication-api?WT.mc_id=AZ-MVP-5003445">here</a>.</p><p>Read <a href="https://medium.com/the-new-control-plane/using-native-authentication-in-entra-external-id-aka-bring-your-own-login-screen-and-login-with-91651cfeefd9">this</a> for more background.</p><p>A “.http” file is a plain-text file supported natively by the VS HTTP client editor (also compatible with the VS Code REST Client extension format) that lets you define, organise, and execute HTTP requests directly from your IDE — without needing external tools like Postman or curl.</p><p>In a .NET project, it’s commonly scaffolded automatically alongside ASP.NET Core Web API projects, giving you a ready-made way to exercise your API’s endpoints during development.</p><p>I have been using Postman to test the API, but when I created another native auth project, I decided to try the .http method.</p><p>As stated above, this is now part of the project. I don’t need to use Postman.</p><p>My .http file to test EEID sign-in via native auth with <strong>username and password</strong> looks like:</p><pre>@BasicApi_HostAddress = http://localhost:5216<br><br>### Native Auth Password<br>@tenant = eeid tenant name e.g. contoso<br>@tenantDomain = contoso.onmicrosoft.com<br>@clientId = dd...b9<br>@username = some-user@company.co.nz<br>@password = some-password<br><br>### Start sign up<br># @name startSignUp<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/oauth2/v2.0/initiate<br>Content-Type: application/x-www-form-urlencoded<br>Host: {{tenant}}.ciamlogin.com<br><br>client_id={{clientId}}&amp;challenge_type=password redirect&amp;username={{username}}<br><br>###<br><br>### Challenge<br># @name challenge<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/oauth2/v2.0/challenge<br>Content-Type: application/x-www-form-urlencoded<br><br>client_id={{clientId}}&amp;challenge_type=password redirect&amp;continuation_token=<br>  {{startSignUp.response.body.$.continuation_token}}<br><br>###<br><br>### Token<br># @name token<br>POST https://{{tenant}}.ciamlogin.com/{{tenantDomain}}/oauth2/v2.0/token<br>Content-Type: application/x-www-form-urlencoded<br><br>client_id={{clientId}}&amp;password={{password}}&amp;continuation_token=<br>  {{challenge.response.body.$.continuation_token}}<br>  &amp;grant_type=password&amp;scope=openid offline_access<br><br>###</pre><p><strong>Note</strong>: The body lines are wrapped here for readability only.</p><p>In VS, it looks like:</p><figure><img alt="Image of the above commands with links for Send and Debug" src="https://cdn-images-1.medium.com/max/1024/1*ilZGX0hSTWNcmbm1GUfwYw.png" /></figure><p>To send the “Start sign up” request, just click “Send request”.</p><p>This displays:</p><figure><img alt="Image of the continuation_token returned from the request" src="https://cdn-images-1.medium.com/max/937/1*_TsKWcNFfjcmj7cnWT2HHA.png" /></figure><p>in a separate tab.</p><p>The “continuation_token” is then passed to the next request. You have to run the requests in the correct order.</p><p>“{{startSignUp.response.body.$.continuation_token}}” is a variable expression that VS resolves at request-send time, right before the request is transmitted.</p><p>This:</p><ul><li>Looks up the named request (`startSignUp`) in the in-memory response cache</li><li>Reads its “response.body”</li><li>Applies the JSONPath “$.continuation_token” to extract the value from the JSON response</li><li>Substitutes the resolved value into the request body/headers/URL wherever the “{{…}}” placeholder appears</li></ul><p>The end result of the “Token” request is:</p><pre>{<br>  &quot;token_type&quot;: &quot;Bearer&quot;,<br>  &quot;scope&quot;: &quot;profile openid email 00000003-0000-0000-c000-00...00/User.Read&quot;,<br>  &quot;expires_in&quot;: 3773,<br>  &quot;ext_expires_in&quot;: 3773,<br>  &quot;access_token&quot;: &quot;ey...eg&quot;,<br>  &quot;refresh_token&quot;: &quot;1....Vg&quot;,<br>  &quot;id_token&quot;: &quot;ey...7g&quot;<br>}</pre><p>Clicking the “Debug” link starts the project under the debugger (if not already running) and immediately fires this request, so breakpoints are hit.</p><p>There’s a lot more functionality, e.g environments and secrets — refer to the docs above.</p><p>Refer to “<a href="https://medium.com/the-new-control-plane/how-to-run-a-http-file-a89bfd87b7a5">How to run a .http file</a>”.</p><figure><a href="https://www.buymeacoffee.com/rbrayb"><img alt="" src="https://cdn-images-1.medium.com/max/281/1*V_86loHl_gBsQqg83gtCLQ.png" /></a></figure><p>All good!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=fa2aeea1a8f2" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-new-control-plane/using-the-net-http-file-to-test-native-auth-in-entra-external-id-eeid-fa2aeea1a8f2">Using the .NET .http file to test native auth in Entra External ID (EEID)</a> was originally published in <a href="https://medium.com/the-new-control-plane">The new control plane</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
        <item>
            <title><![CDATA[Using the Entra ID Agentic ID sample]]></title>
            <link>https://medium.com/the-new-control-plane/using-the-entra-id-agentic-id-sample-0a1ab1b818de?source=rss-6601e21c1210------2</link>
            <guid isPermaLink="false">https://medium.com/p/0a1ab1b818de</guid>
            <category><![CDATA[agentic-user]]></category>
            <category><![CDATA[github]]></category>
            <category><![CDATA[entra-id]]></category>
            <category><![CDATA[agentic-id]]></category>
            <category><![CDATA[blueprint]]></category>
            <dc:creator><![CDATA[Rory Braybrook]]></dc:creator>
            <pubDate>Fri, 28 Aug 2026 01:31:07 GMT</pubDate>
            <atom:updated>2026-08-28T20:45:03.723Z</atom:updated>
            <content:encoded><![CDATA[<figure><img alt="Image of agentic ID" src="https://cdn-images-1.medium.com/max/300/1*z7yQVlmMvQbJiWwkovDUgA.png" /><figcaption>AI generated</figcaption></figure><p>The repo is <a href="https://github.com/Azure-Samples/ms-identity-agent-identities/tree/main">here</a>.</p><p>“This repository contains samples demonstrating how to use Agent Identities in Microsoft Entra ID with .NET. Agent Identities enable AI agents to securely access downstream services using either autonomous (app-only) or user-delegated tokens”.</p><p>“Agent Identities are a new capability announced at Ignite 2025 in Microsoft Entra ID that enable AI agents to:</p><ul><li>Autonomous Agent Identity — Acquire app-only tokens for operations that don’t require user context.</li><li>Agent User Identity<strong> </strong>— Acquire tokens with user context for operations requiring user identity (e.g., sending emails, participating in Teams channels)”</li></ul><p>The Learn article on agent ID is <a href="https://learn.microsoft.com/en-us/entra/agent-id?WT.mc_id=AZ-MVP-5003445">here</a>.</p><p>Essentially, you define blueprints that contain agent IDs, each with a sponsor and an owner for auditability.</p><p>In the repo, there is a /scripts directory.</p><pre>.\Setup-EntraIdApps.ps1 -TenantId ae...3b -OutputFormat UpdateConfig</pre><p>This produces:</p><pre>========================================<br>Entra ID Agent Identity Blueprint Setup<br>Customer Service Agent Sample<br>========================================<br><br>Instance Prefix: CustomerServiceSample-<br><br>[INFO] Checking Microsoft Graph connection...<br>[INFO] Not connected to Microsoft Graph. Connecting...<br>[SUCCESS] Connected to tenant: ae...3b<br>[INFO] Current user: rory@entra-tenant.onmicrosoft.com (will be set as <br>  SponsorUserId)<br><br>--- Step 1: Creating Agent Identity Blueprint ---<br><br>[INFO] Using specialized Agent Identity Blueprint endpoint...<br>[INFO] Creating Agent Identity Blueprint application: <br>  CustomerServiceSample-Orchestrator<br>[INFO] Creating Agent Identity Blueprint using beta endpoint...<br>[SUCCESS] Successfully created Agent Identity Blueprint<br>[INFO] Blueprint Application ID: 2a..4d<br>[INFO] Blueprint App ID (Client ID): 2a..4d<br>[WARNING] Could not create Agent Identity Blueprint using API. Falling back <br>  to standard application.<br>[WARNING] Error: The property &#39;scopes&#39; cannot be found on this object. <br>  Verify that the property exists.<br>[INFO] Checking for existing app: CustomerServiceSample-Orchestrator<br>[SUCCESS] Found existing app: CustomerServiceSample-Orchestrator <br>  (ClientId: 2a...4d)<br><br>--- Step 5: Creating Service Principal for Blueprint ---<br><br>[INFO] Creating service principal for blueprint...<br>Making request to create service principal for Agent Blueprint: <br>  System.Collections.Hashtable.Orchestrator.ClientId<br>[SUCCESS] Service principal created successfully<br><br>--- Step 2: Adding Client Secret to Blueprint ---<br><br><br>--- Step 3: Creating Downstream Service Applications ---<br><br>[INFO] Processing service: CustomerServiceSample-OrderAPI<br>[INFO] Checking for existing app: CustomerServiceSample-OrderAPI<br>[INFO] Creating new app: CustomerServiceSample-OrderAPI<br>[SUCCESS] Created app: CustomerServiceSample-OrderAPI <br>  (ClientId: 18...73)<br>[INFO] Configuring API scopes...<br>[INFO] Setting Application ID URI: api://18...73<br>[SUCCESS] Adding scope: Orders.Read<br>[INFO] Updating OAuth2 permission scopes...<br>[SUCCESS] API scopes configured successfully<br>[INFO] Configuring app roles...<br>[SUCCESS] Adding app role: Orders.Read.All<br>[INFO] Updating app roles...<br>[SUCCESS] App roles configured successfully<br>[SUCCESS] Service CustomerServiceSample-OrderAPI configured successfully<br><br>[INFO] Processing service: CustomerServiceSample-ShippingAPI<br>[INFO] Checking for existing app: CustomerServiceSample-ShippingAPI<br>[INFO] Creating new app: CustomerServiceSample-ShippingAPI<br>[SUCCESS] Created app: CustomerServiceSample-ShippingAPI <br>  (ClientId: b2...7f)<br>[INFO] Configuring API scopes...<br>[INFO] Setting Application ID URI: api://b2...7f<br>[SUCCESS] Adding scope: Shipping.Read<br>[SUCCESS] Adding scope: Shipping.Write<br>[INFO] Updating OAuth2 permission scopes...<br>[SUCCESS] API scopes configured successfully<br>[SUCCESS] Service CustomerServiceSample-ShippingAPI configured successfully<br><br>[INFO] Processing service: CustomerServiceSample-EmailAPI<br>[INFO] Checking for existing app: CustomerServiceSample-EmailAPI<br>[INFO] Creating new app: CustomerServiceSample-EmailAPI<br>[SUCCESS] Created app: CustomerServiceSample-EmailAPI <br>  (ClientId: 35...65)<br>[INFO] Configuring API scopes...<br>[INFO] Setting Application ID URI: api://35...65<br>[SUCCESS] Adding scope: Email.Send<br>[INFO] Updating OAuth2 permission scopes...<br>[SUCCESS] API scopes configured successfully<br>[SUCCESS] Service CustomerServiceSample-EmailAPI configured successfully<br><br><br>--- Step 4: Configuring Inheritable Permissions for Blueprint ---<br><br>[INFO] Setting up inheritable permissions to downstream APIs...<br>[INFO] Configuring inheritable permissions for Agent Identity Blueprint...<br>[INFO] Creating service principal for: CustomerServiceSample-OrderAPI<br>[INFO] Creating service principal for: CustomerServiceSample-ShippingAPI<br>[INFO] Creating service principal for: CustomerServiceSample-EmailAPI<br>Successfully added inheritable permissions to Agent Identity Blueprints<br>Permissions are now available for inheritance by agent blueprints<br><br>--- Step 6: Granting Admin Consent for Inheritable Permissions ---<br><br>[INFO] Granting admin consent for orchestrator app...<br>[SUCCESS] Admin consent granted successfully<br><br><br>========================================<br>Setup Completed Successfully!<br>========================================<br><br><br>--- Updating Configuration Files ---<br><br>[INFO] Setting SponsorUserId to current user: <br>  rory@entra-tenant.onmicrosoft.com (5c...64)<br>[INFO] Detected tenant domain: entra-tenant.onmicrosoft.com<br>[INFO] Updating: C:\...\ms-identity-agent-identities\<br>  dotnet\CustomerServiceAgent\src\AgentOrchestrator\appsettings.json<br>[SUCCESS] Updated successfully<br>[INFO]<br>Configuring client secret using User Secrets...<br>Successfully saved AzureAd:ClientCredentials:0:ClientSecret to the secret <br>  store.<br>[SUCCESS] Successfully configured client secret in User Secrets<br>[INFO] The secret is stored securely outside of source control<br>[INFO] For production, use Azure Key Vault or environment variables<br>[INFO] See SECRETS-MANAGEMENT.md for detailed instructions<br><br>[INFO] Updating: C:\...\ms-identity-agent-identities\dotnet\<br>  CustomerServiceAgent\src\AgentOrchestrator\AgentOrchestrator.http<br>[SUCCESS] Updated TenantName to: entra-tenant.onmicrosoft.com<br>[INFO] Updating: C:\...\ms-identity-agent-identities\dotnet\<br>  CustomerServiceAgent\src\DownstreamServices\EmailService\appsettings.json<br>[SUCCESS] Updated successfully<br>[INFO] Updating: C:\...\ms-identity-agent-identities\dotnet\<br>  CustomerServiceAgent\src\DownstreamServices\OrderService\appsettings.json<br>[SUCCESS] Updated successfully<br>[INFO] Updating: C:\...\ms-identity-agent-identities\dotnet\<br>  CustomerServiceAgent\src\DownstreamServices\ShippingService\appsettings.json<br>[SUCCESS] Updated successfully<br>[SUCCESS]<br>All configuration files updated!<br><br>--- Next Steps ---<br><br>[INFO] 1. Review the configuration values above<br>[INFO] 5. Verify the updated appsettings.json files<br>[INFO] 6. Build and run the solution: <br>  dotnet run --project src/CustomerServiceAgent.AppHost<br>[INFO] For detailed setup instructions, see: docs/setup/02-entra-id-setup.md<br>DeletedDateTime Id    AppRoleId     CreatedDateTime<br>--------------- --    ---------     ---------------<br>                6t...Bc 4a...14 25/08/2026 3:25:53 am<br></pre><p>This creates a blueprint (under the Agents tab).</p><p><strong>Note</strong>: This blueprint is contained under the “Agents” tab in the portal rather than the classic app registrations list view, but it is backed by a standard application object underneath.</p><p>The blueprint establishes the “kind” of agent and records metadata shared across all agent identities of a common kind.</p><figure><img alt="Image of blueprint with three agent identities" src="https://cdn-images-1.medium.com/proxy/0*TrxC7H0Ohmrhd-9F.png" /></figure><p>The blueprint tab looks like:</p><figure><img alt="Image of blueprint orchestrator page in portal" src="https://cdn-images-1.medium.com/max/1024/1*_cq1LuKEI-ugc-x_ehsUuQ.png" /></figure><p>with me as the owner and sponsor:</p><figure><img alt="Image of blueprint owner and sponsor" src="https://cdn-images-1.medium.com/max/1024/1*onodaebC2J6q7UpiplzyFw.png" /></figure><p>and the permission:</p><figure><img alt="Image of AgentIdUser.ReadWrite permission" src="https://cdn-images-1.medium.com/max/1024/1*lCGLFFUKAslzINkt7fwiCA.png" /></figure><p>If you click on “Credentials”, you see a client secret.</p><figure><img alt="Image of blueprint client secret" src="https://cdn-images-1.medium.com/max/1024/1*9rd6VXFeO_VEnVShTm3RFw.png" /></figure><p>This is the secret created at the end of the script.</p><p>The secret is stored using .NET <a href="https://learn.microsoft.com/en-us/aspnet/core/security/app-secrets?view=aspnetcore-10.0&amp;tabs=windows%2Cpowershell&amp;WT.mc_id=AZ-MVP-5003445">User Secrets</a>.</p><p>To see the secrets:</p><pre>PS C:\...\ms-identity-agent-identities\dotnet\CustomerServiceAgent\src\<br>AgentOrchestrator&gt; dotnet user-secrets list<br>AzureAd:ClientCredentials:0:ClientSecret = 27...ak</pre><p>In the blueprint, if you click on agent identities:</p><figure><img alt="" src="https://cdn-images-1.medium.com/max/509/1*S62p8VMQI18-jHS-A-J-0w.png" /></figure><p>you see:</p><figure><img alt="Image of agent ID inside blueprint" src="https://cdn-images-1.medium.com/max/977/1*xOzYiygBQy3F6_dho8Ulkw.png" /></figure><p>and if you click on “agent identity1”:</p><figure><img alt="Image of agent identity page" src="https://cdn-images-1.medium.com/max/1024/1*zGVjtR2F2KrAfg7IJkcPDQ.png" /></figure><p>you see an agent identity that points back to the blueprint.</p><p>Each agent ID has a corresponding <a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-users?WT.mc_id=AZ-MVP-5003445">agent user</a>.</p><p>If you click on “View agent’s user account”, you see the agent&#39;s user account:</p><figure><img alt="Image of agent user page" src="https://cdn-images-1.medium.com/max/995/1*KlE456F9G153pKg4XRhCJQ.png" /></figure><p>If you click on “View” next to “Agent identity&#39;s access”, you see the permissions:</p><figure><img alt="Image of agent identities access permissions e.g. Email.Send, Orders.ReadAll" src="https://cdn-images-1.medium.com/max/1024/1*tKu76C_EZtmFb021oSWArg.png" /></figure><p>These correspond to the app registrations:</p><figure><img alt="Image of three app registrations — email, order and shipping" src="https://cdn-images-1.medium.com/max/379/1*OOZSZ8xretRk_E436NM4TA.png" /></figure><p>Each of these is an API that the orchestrator calls.</p><p>If you look at OrderAPI e.g., you see under “Expose an API”:</p><figure><img alt="Image of app registration — Expose and API — api://…" src="https://cdn-images-1.medium.com/max/1024/1*NiWPhl3B26JjLkUzU0YSvA.png" /></figure><p>The script also configures the appsettings.json:</p><pre>{<br>  &quot;Logging&quot;: {<br>    &quot;LogLevel&quot;: {<br>      &quot;Default&quot;: &quot;Information&quot;,<br>      &quot;Microsoft.AspNetCore&quot;: &quot;Warning&quot;,<br>      &quot;Microsoft.Identity&quot;: &quot;Debug&quot;<br>    }<br>  },<br>  &quot;AllowedHosts&quot;: &quot;*&quot;,<br>  &quot;AzureAd&quot;: {<br>    &quot;Instance&quot;: &quot;https://login.microsoftonline.com/&quot;,<br>    &quot;TenantId&quot;: &quot;ae...3b&quot;,<br>    &quot;ClientId&quot;: &quot;2a...4d&quot;,<br>    &quot;ClientCredentials&quot;: [<br>      {<br>        &quot;SourceType&quot;: &quot;ClientSecret&quot;<br>      }<br>    ],<br>    &quot;CallbackPath&quot;: &quot;/signin-oidc&quot;<br>  },<br>  &quot;AgentIdentities&quot;: {<br>    &quot;AgentIdentity&quot;: &quot;YOUR_AGENT_IDENTITY_ID&quot;,<br>    &quot;AgentUserId&quot;: &quot;YOUR_AGENT_USER_ID&quot;,<br>    &quot;SponsorUserId&quot;: &quot;5c...64&quot;<br>  },<br>  &quot;DownstreamApis&quot;: {<br>    &quot;OrderService&quot;: {<br>      &quot;BaseUrl&quot;: &quot;https://localhost:7001&quot;,<br>      &quot;Scopes&quot;: [<br>        &quot;api://18...73/.default&quot;<br>      ]<br>    },<br>    &quot;ShippingService&quot;: {<br>      &quot;BaseUrl&quot;: &quot;https://localhost:7003&quot;,<br>      &quot;Scopes&quot;: [<br>        &quot;api://b2...7f/.default&quot;<br>      ]<br>    },<br>    &quot;EmailService&quot;: {<br>      &quot;BaseUrl&quot;: &quot;https://localhost:7004&quot;,<br>      &quot;Scopes&quot;: [<br>        &quot;api://35...65/.default&quot;<br>      ]<br>    },<br>    &quot;MicrosoftGraph&quot;: {<br>      &quot;BaseUrl&quot;: &quot;https://graph.microsoft.com/v1.0&quot;,<br>      &quot;Scopes&quot;: &quot;User.Read Mail.Send ChannelMessage.Send&quot;<br>    }<br>  }<br>}</pre><p>So what we have is:</p><ul><li>An Agent Orchestrator service that acts as the AI agent, creating an “agent blueprint,” an autonomous agent identity, and an agent user identity</li><li>Calls to multiple downstream APIs (Order, Shipping, Email) using either app-only or delegated tokens depending on the operation</li><li>.NET Aspire used for orchestration plus distributed tracing/logging, so you can visually observe how each downstream call’s identity/token flow works via the Aspire dashboard</li><li>In-memory stores, so it runs without external dependencies (aside from Entra ID app registrations)</li><li>The AgentOrchestrator.http file is used to manually exercise the flow: first call api/agentidentity to create/get an agent identity, then use that identity’s GUID to call api/customerservice/process, which triggers the orchestrator to call the downstream APIs on behalf of the agent</li></ul><p>The .http file looks like:</p><figure><img alt="Image of API calls e.g. Send request — POST {{AgentOrchestrator_HostAddress}}/api/agentidentity?agentIdentityName=agent%20identity1&amp;agentUserIdentityUpn={{AgentUserName}}@{{TenantName}} Accept: application/json Content-Type: application/json" src="https://cdn-images-1.medium.com/max/841/1*F5DLPzfoPsVJMX5TKjEcbQ.png" /></figure><p>“Send request” is a hyperlink that executes the request.</p><p>So it&#39;s similar to Postman.</p><p>Run the sample. You should see the Aspire portal:</p><figure><img alt="Image of Aspire portal page" src="https://cdn-images-1.medium.com/max/1024/1*ANZm5GI3BR_UIu13WLeriw.png" /></figure><p>The URLs won’t work because the services are just API. There is no website.</p><p>You can use this to inspect the traces if there are issues.</p><p>In the .http file, click “Send request” for the top call:</p><pre>/api/agentidentity</pre><p>You should see:</p><pre>{<br>  &quot;agentIdentity&quot;: {<br>    &quot;@odata.type&quot;: &quot;#Microsoft.Graph.AgentIdentity&quot;,<br>    &quot;displayName&quot;: &quot;agent identity1&quot;,<br>    &quot;agentIdentityBlueprintId&quot;: &quot;2a...4d&quot;,<br>    &quot;id&quot;: &quot;54...58&quot;,<br>    &quot;sponsors@odata.bind&quot;: null,<br>    &quot;owners@odata.bind&quot;: null<br>  },<br>  &quot;agentUserIdentity&quot;: {<br>    &quot;@odata.type&quot;: &quot;#microsoft.graph.agentUser&quot;,<br>    &quot;displayName&quot;: &quot;agentuser1&quot;,<br>    &quot;userPrincipalName&quot;: &quot;agentuser1@entra-tenant.onmicrosoft.com&quot;,<br>    &quot;id&quot;: &quot;3f...70&quot;,<br>    &quot;identityParentId&quot;: &quot;54...58&quot;,<br>    &quot;mailNickname&quot;: &quot;agentuser1&quot;,<br>    &quot;accountEnabled&quot;: true<br>  },<br>  &quot;adminConsentUrlScopes&quot;: &quot;https://login.microsoftonline.com/<br>    ae...3b/v2.0/adminconsent?client_id=54...58&amp;<br>    scope=api%3A%2F%2F...65%2FEmail.Send%20api%3A%2F%2F...73<br>    %2FOrders.Read%20api%3A%2F%2F...7f<br>    %2FShipping.Read%20api%3A%2F%2F...7f%2FShipping.Write<br>    &amp;redirect_uri=https://entra.microsoft.com/TokenAuthorize&amp;state=xyz123&quot;,<br>  &quot;adminConsentUrlRoles&quot;: &quot;https://login.microsoftonline.com/<br>    ae...3b/v2.0/adminconsent?client_id=54...58<br>    &amp;role=api%3A%2F%2F...73%2FOrders.Read.All<br>    &amp;redirect_uri=https://entra.microsoft.com/TokenAuthorize&amp;state=xyz123&quot;</pre><p>Now copy the agentIdentity ID (“54…58”) into the @AgentIdentity line in the .http file (as per the image above) and save.</p><p>Then in the browser, execute the two HTTPS URLs above (“adminConsentUrlScopes” and “adminConsentUrlRoles”) in separate tabs. I was logged on as a GA for these.</p><p>Then grant permission on all the screens by clicking “Allow&quot; e.g.</p><figure><img alt="Image of grant — Allow this application to access data" src="https://cdn-images-1.medium.com/max/500/1*e0FKbPT9vKF3_q9FnsUwHA.png" /></figure><p>and:</p><figure><img alt="Image of grant — Allow this agent to access data" src="https://cdn-images-1.medium.com/max/503/1*NjL7VBOBUWdNqRofymKAXw.png" /></figure><p>There are a few of these.</p><p>Now click “Send request” for the second call:</p><pre>/api/customerservice/process</pre><p>You should see:</p><pre>{<br>  &quot;orderId&quot;: &quot;12345&quot;,<br>  &quot;status&quot;: &quot;Completed&quot;,<br>  &quot;orderDetails&quot;: {<br>    &quot;orderId&quot;: &quot;12345&quot;,<br>    &quot;customerId&quot;: &quot;C001&quot;,<br>    &quot;productName&quot;: &quot;Enterprise Software License&quot;,<br>    &quot;quantity&quot;: 10,<br>    &quot;price&quot;: 9999.99,<br>    &quot;status&quot;: &quot;Processing&quot;,<br>    &quot;orderDate&quot;: &quot;2026-08-23T03:34:10.3914844Z&quot;<br>  },<br>  &quot;customerHistory&quot;: null,<br>  &quot;deliveryInfo&quot;: {<br>    &quot;orderId&quot;: &quot;12345&quot;,<br>    &quot;trackingNumber&quot;: &quot;TRK-UPDATED-2E109F08&quot;,<br>    &quot;status&quot;: &quot;Updated by Agent&quot;,<br>    &quot;carrier&quot;: &quot;Express Delivery&quot;,<br>    &quot;estimatedDelivery&quot;: &quot;2026-08-26T03:34:10.4442694Z&quot;,<br>    &quot;shippingAddress&quot;: &quot;Updated via orchestration&quot;<br>  },<br>  &quot;emailSent&quot;: true,<br>  &quot;teamsMessagePosted&quot;: false,<br>  &quot;messages&quot;: [<br>    &quot;Fetching order details using agent identity...&quot;,<br>    &quot;Order 12345 retrieved successfully&quot;,<br>    &quot;Updating delivery info using agent user identity...&quot;,<br>    &quot;Delivery info updated successfully&quot;,<br>    &quot;Sending email notification using agent user identity...&quot;,<br>    &quot;Email notification sent successfully&quot;,<br>    &quot;Customer service request processed successfully&quot;<br>  ]<br>}</pre><p>The email notification is just a dummy. No actual email is sent.</p><p>The flow uses two OIDC grant types:</p><p>“client_credentials” for the Autonomous Agent Identity (app-only)</p><p>and:</p><p>“urn:ietf:params:oauth:grant-type:user-fic” (user Federated Identity Credential) — for the Agent User Identity</p><p>In the agent Identities model, an agent Identity doesn’t get its own manually managed client secret or certificate.</p><p>Instead, it trusts its parent blueprint application to vouch for it. This trust relationship is the <strong>Federated Identity Credential </strong>— the agent identity is federated to its parent.</p><p>In a diagram:</p><p>Blueprint App (Application object, the Orchestrator) <br> │ (agentIdentityBlueprintId reference)<br> ▼<br>Agent Identity (Service Principal, type = Microsoft.Graph.AgentIdentity)<br> │ (identityParentId reference)<br> ▼<br>Agent User Identity (also created via Graph, linked to the Agent Identity)</p><p>For more FIC details, refer to <a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-oauth-protocols?WT.mc_id=AZ-MVP-5003445">this</a> and <a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-identities?WT.mc_id=AZ-MVP-5003445">this</a> and <a href="https://learn.microsoft.com/en-us/entra/agent-id/agent-users?WT.mc_id=AZ-MVP-5003445">this</a>.</p><p>An example of the client credentials JWT is:</p><pre>{<br>  &quot;aud&quot;: &quot;api://18...73&quot;,<br>  &quot;iss&quot;: &quot;https://sts.windows.net/ae...3b/&quot;,<br>  &quot;iat&quot;: 1787629639,<br>  &quot;nbf&quot;: 1787629639,<br>  &quot;exp&quot;: 1787633539,<br>  &quot;aio&quot;: &quot;k2...YA&quot;,<br>  &quot;appid&quot;: &quot;54...58&quot;,<br>  &quot;appidacr&quot;: &quot;2&quot;,<br>  &quot;idp&quot;: &quot;https://sts.windows.net/ae...3b/&quot;,<br>  &quot;oid&quot;: &quot;54...58&quot;,<br>  &quot;rh&quot;: &quot;1.A...AA.&quot;,<br>  &quot;roles&quot;: [<br>    &quot;Orders.Read.All&quot;<br>  ],<br>  &quot;sub&quot;: &quot;54...58&quot;,<br>  &quot;tid&quot;: &quot;ae...3b&quot;,<br>  &quot;uti&quot;: &quot;4E...AA&quot;,<br>  &quot;ver&quot;: &quot;1.0&quot;,<br>  &quot;xms_ftd&quot;: &quot;WR...1z&quot;<br>}</pre><p>An example of the FIC JWT is:</p><pre>{<br>  &quot;aud&quot;: &quot;api://35...65&quot;,<br>  &quot;iss&quot;: &quot;https://sts.windows.net/ae...3b/&quot;,<br>  &quot;iat&quot;: 1787629642,<br>  &quot;nbf&quot;: 1787629642,<br>  &quot;exp&quot;: 1787633951,<br>  &quot;acr&quot;: &quot;0&quot;,<br>  &quot;aio&quot;: &quot;AZ...4V&quot;,<br>  &quot;appid&quot;: &quot;54...58&quot;,<br>  &quot;appidacr&quot;: &quot;2&quot;,<br>  &quot;ipaddr&quot;: &quot;www.xxx.yy.zzz&quot;,<br>  &quot;name&quot;: &quot;agentuser1&quot;,<br>  &quot;oid&quot;: &quot;3f...70&quot;,<br>  &quot;rh&quot;: &quot;1.A...AA.&quot;,<br>  &quot;scp&quot;: &quot;Email.Send&quot;,<br>  &quot;sid&quot;: &quot;007...3b7&quot;,<br>  &quot;sub&quot;: &quot;Mx...00&quot;,<br>  &quot;tid&quot;: &quot;ae...3b&quot;,<br>  &quot;unique_name&quot;: &quot;agentuser1@entra-tenant.onmicrosoft.com&quot;,<br>  &quot;upn&quot;: &quot;agentuser1@entra-tenant.onmicrosoft.com&quot;,<br>  &quot;uti&quot;: &quot;P_8...cAA&quot;,<br>  &quot;ver&quot;: &quot;1.0&quot;,<br>  &quot;xms_ftd&quot;: &quot;2LS...21z&quot;<br>}</pre><p>In table form:</p><iframe src="" width="0" height="0" frameborder="0" scrolling="no"><a href="https://medium.com/media/f9c3eb60453e373c6b2ec50142e22598/href">https://medium.com/media/f9c3eb60453e373c6b2ec50142e22598/href</a></iframe><p>This maps directly to the pattern demonstrated in the sample:</p><ul><li>reads use the autonomous (app-only) agent identity via “client_credentials”</li><li>writes that must be attributed to a specific user use the agent user identity via the “user-fic” grant</li></ul><p><strong>Note</strong>: I tried to get the sample working with Entra External ID (EEID), but although the script runs without issues and EEID does have an Agents tab in the portal, I get errors like:</p><ul><li>IDX10214: Audience validation failed. See <a href="https://aka.ms/identitymodel/app-context-switches">https://aka.ms/identitymodel/app-context-switches</a></li><li>Error: AADSTS500208: The domain is not a valid login domain for the account type</li></ul><p>I cannot find any references confirming whether agents and agentic IDs are supported in EEID.</p><figure><a href="https://www.buymeacoffee.com/rbrayb"><img alt="" src="https://cdn-images-1.medium.com/max/281/1*V_86loHl_gBsQqg83gtCLQ.png" /></a></figure><p>All good!</p><img src="https://medium.com/_/stat?event=post.clientViewed&referrerSource=full_rss&postId=0a1ab1b818de" width="1" height="1" alt=""><hr><p><a href="https://medium.com/the-new-control-plane/using-the-entra-id-agentic-id-sample-0a1ab1b818de">Using the Entra ID Agentic ID sample</a> was originally published in <a href="https://medium.com/the-new-control-plane">The new control plane</a> on Medium, where people are continuing the conversation by highlighting and responding to this story.</p>]]></content:encoded>
        </item>
    </channel>
</rss>