Federal Modernization Workbook
Chapter 05 — Workload Identity Federation and Managed Identities
A federated credential with the wrong subject is accepted the day it’s created and fails the day the workload depends on it, so the check belongs before the cutover, in code a reviewer has read.
The conversion behind the project plan’s tasks NP3.2 to NP3.4: workload identities created in the plan’s order (a managed identity, then a federated credential, then a certificate), each federated credential’s token exchange tested, then a 30-day parallel run before the legacy account is disabled. You start with Learn’s own samples for creating federated identity credentials, on an app registration and on a user-assigned managed identity, and the Managed Identity REST examples Learn’s reference renders, all pinned in the repository, and you run them against fakes to see what they do and what they miss. Then you use examples/non-person-identity for what they leave to you: the credentials as reviewed JSON, a check of each one against the claims the workload’s issuer will present, a token exchange test against a fake token endpoint, managed identities created or recorded with their role assignments, and the parallel run tracked from sign-in evidence, applied only through a separate, delegated write path after a reviewer approves the plan’s SHA-256. Lab 1 runs on a laptop with no tenant; Lab 2 uses a test tenant, a test subscription and a test workflow.
Jump to a slide (18)
Deck · Narrated deck (about 12 minutes, captions) · Script · Captions. Recorded on November 2, 2026 from the approved chapter.
Goal and audience
Goal. By the end of this chapter you can hold a wave’s conversions as one reviewed file, and from it, in code: create the user-assigned managed identities and federated credentials the plan’s task NP3.2 asks for (“workload identities created (managed identity, then federated credential, then certificate); federated token exchange tested”), show before any cutover that each credential will match the token its workload presents, and track NP3.3’s and NP3.4’s parallel runs (“old and new credentials side by side for 30 days, then the legacy accounts disabled”) to the disable. You can say, for each rule, whether it is Learn’s, Learn’s sample, or the program’s design.
Audience. Developers who build the project’s conversion code, the identity engineers who run the waves, and the application owners who switch each workload and approve each disable. You should read PowerShell 7 and know Chapters 01 and 02. You don’t need a tenant until Lab 2.
What the code depends on
The order. Book 02’s Phase 4 converts HIGH-risk accounts first and creates “managed identities for Azure-hosted workloads, federated credentials for pipelines and other OIDC-capable hosts, certificates only where neither applies” (Book 02, Chapter 10). The plan’s decision D-NP3 names the target for each account, and Chapter 03 builds the waves. This chapter takes the waves as given and holds the credentials.
What a federated credential is. A federated identity credential on an app registration or a user-assigned managed identity tells Microsoft Entra ID to trust tokens from an external issuer: GitHub Actions, a Kubernetes cluster, another cloud or any OIDC provider. The workload presents its issuer’s token, and Entra ID exchanges it for an access token (Book 02, Chapter 09).1 Three values decide the match. The issuer must match the token’s iss claim, the subject its sub claim, and the audience its aud claim; the audience is a single value, and for Entra ID it is api://AzureADTokenExchange, the value Learn recommends and the Graph reference says to set.2 3 The match is exact and case-sensitive.4 Wildcards aren’t supported in any value; an issuer claim with leading or trailing whitespace blocks the exchange; a name is 3 to 120 characters of letters, digits, dashes or underscores, starting with a letter or digit, and can’t be changed.5 An app registration or a user-assigned managed identity holds at most 20.6 A system-assigned managed identity can’t hold one at all.7
The risk this chapter exists for. Learn says it twice: a federated credential with the wrong subject “is created successfully without error,” and the error appears only when the token exchange fails; for GitHub, the exchange “fails without error.”8 The plan’s risk register says the same, and its mitigation is to test each credential’s token exchange before the parallel run starts (project plan, risks). That test is the center of this chapter.
Managed identities. A workload hosted in Azure uses a managed identity, and Azure manages its credential.9 A user-assigned identity is created on its own and assigned to its hosts; creating one takes Managed Identity Contributor.10 A system-assigned identity is turned on in the host’s own deployment, and lives and dies with it. Either way, its access comes from Azure role assignments, which this chapter records and Chapter 07 narrows.
The parallel run. The implementation guide’s Step 5 runs old and new together for 30 days with the legacy account still enabled, watching the new identity’s sign-ins in Entra ID and the legacy account’s logons on the domain controllers, then disables the legacy account, never deletes it. For an app registration losing a secret, it removes the secret only after the new credential has carried production traffic (implementation guide, Step 5). Book 02’s rollback is to re-enable the legacy account and revert the application’s configuration.
Which cloud, for these reads and writes
The target estate is Microsoft 365 GCC with commercial Azure: Graph at graph.microsoft.com, Resource Manager at management.azure.com, sign-in at login.microsoftonline.com. GCC High and DoD use graph.microsoft.us and dod-graph.microsoft.us, Azure Government’s management.usgovcloudapi.net, and login.microsoftonline.us.11 12 Creating a federated credential on an application and listing sign-ins are both available in US Government L4 and L5.13 14 The Resource Manager pages read for user-assigned identities don’t state Azure Government availability; this estate’s Azure is commercial, so the code sends them to management.azure.com for gcc, and to Azure Government only for gcchigh and dod. The token exchange test goes only to the fake, but the request it builds is addressed to the cloud’s own sign-in host. One limit applies to GCC High and DoD: Learn gives Entra ID for US Government a different token-exchange audience, api://AzureADTokenExchangeUSGov, and NPI-WIF-04 accepts only api://AzureADTokenExchange, so the code is not ready for those clouds until that check changes (Chapter 11 cites the source).
Start with Microsoft’s sample
What Microsoft gives you
Learn’s Configure an app to trust an external identity provider is Microsoft’s procedure for a federated credential on an app registration. It has samples for the admin center, the Azure CLI (az ad app federated-credential create with a credential.json body for GitHub Actions, Kubernetes and another provider), Azure PowerShell (New-AzADAppFederatedCredential) and Microsoft Graph through az rest.15 Its sibling, Configure a user-assigned managed identity to trust an external identity provider, does the same for a user-assigned managed identity, with the Azure CLI, Azure PowerShell, an ARM template that creates the identity and its credential, and a REST request.16 Both pages share an include with the rules above.17 The Graph reference Create federatedIdentityCredential has the HTTP request that creates one on an application.18 The Managed Identity REST reference renders Microsoft’s examples for creating a federated credential on a user-assigned identity, and for creating and reading the identity, at API version 2024-11-30.19 20
The pinned copies
The samples exist only inside those pages and the REST examples, so the repository pins them unmodified:
examples/third_party/entra-wif-create-trust-pages: both Learn pages and their shared include, from MicrosoftDocs/entra-docs at commitd75a90e(February 25, 2026, the last change to all three; unchanged on October 30, 2026), with the repository’s licenses (MIT for code, Creative Commons Attribution 4.0 for documentation).21examples/third_party/graph-fic-create-page: the Graph reference page’s source from microsoftgraph/microsoft-graph-docs-contrib at commit08b242e(December 4, 2025; unchanged on October 30, 2026), same licenses.22examples/third_party/msi-fic-rest-examples:FederatedIdentityCredentialCreate.json,IdentityCreate.jsonandIdentityGet.jsonfrom Azure/azure-rest-api-specs at commit37886ac(September 12, 2025; unchanged on October 30, 2026), MIT.23
Each folder’s snapshot.json records every file’s SHA-256, checked against the upstream git blob hashes, and a unit test checks them. The weekly source watch reports new commits to each upstream path. Updating a copy is a reviewed pull request.
tests/FederationSampleHarness.ps1 extracts the samples at test time: it checks the credential.json bodies and the template’s audience with the chapter’s credential checks, runs the Azure PowerShell command against a stand-in that records its parameters, sends the Graph, az rest and REST requests to the fake Graph and Resource Manager, and exchanges test assertions against the fake token endpoint.
What they leave to you
Each row is pinned by a test: in tests/NpiFederationSamples.Tests.ps1 for the samples, and in tests/NpiFederation.Tests.ps1 for the example.
| Microsoft’s sample, as published | Why it matters | What examples/non-person-identity adds |
|---|---|---|
| Graph creates a credential from name, issuer, subject and the one audience | — | Kept as the model for the application write; the fake enforces the creation errors Learn documents and nothing else |
| The Graph sample is accepted with any subject; a wrong one fails only at token exchange | The plan’s risk, shown on Microsoft’s own sample | Each credential is checked against the claims its workload presents, then exchanged against the fake token endpoint, before it’s created (NPI-WIF-02, -08) |
The Azure PowerShell GitHub sample uses https://token.actions.githubusercontent.com/, with a trailing slash; the CLI and REST create samples don’t |
Learn’s own samples disagree, and the match is exact24 | The issuer is compared exactly with what the workload’s issuer presents (NPI-WIF-03; research gaps, row 41) |
The managed identity page’s ARM template defaults the audience to " api://AzureADTokenExchange", with a leading space |
A credential that can never match, accepted at creation | Exactly one audience, exactly api://AzureADTokenExchange (NPI-WIF-04) |
GitHub subjects are given as formats; a pull request’s is repo:<org/repo>:pull-request on the Entra page and repo:<org/repo>:pull_request on App Service’s page25 |
Two Learn pages, two answers; GitHub’s newer immutable format can’t be derived at all26 | Subjects derived only for environment, branch and tag in the name-based format; anything else needs the sub claim captured from a test run (research gaps, rows 42 and 43) |
The az rest samples call https://graph.microsoft.com/applications/..., with no version segment |
A request a reviewer can’t place on v1.0 or beta | The code builds every URL from the call list, on /v1.0 or /beta (research gaps, row 48) |
| The managed identity page’s REST sample uses API version 2022-01-31-preview, and its curl payload isn’t valid JSON | Preview versions in production code; a sample that can’t run as written | The code uses the reference’s 2024-11-30, which the pinned REST examples show (row 48) |
Hard-codes graph.microsoft.com and management.azure.com |
Wrong for GCC High and DoD | Hosts from the cloud table, checked on every request |
| One credential at a time, by hand; nothing compares the tenant with what was reviewed | Drift: a credential added in the portal is invisible | The reviewed file diffed against the tenant: create, no change, review, and unmanaged credentials reported, never deleted (NPI-WIF-07) |
| Nothing about the order, the 20-credential limit, a repeated issuer and subject, or a system-assigned identity | Learn states the limits;27 the samples don’t check them | NPI-WIF-01, -05, -06 before anything is sent |
| No managed identity record, no role assignments read, no parallel run, no disable | NP3.3 and NP3.4 are left to you | Identities created or recorded with their role assignments; the 30-day run from sign-in evidence; the disable through the reviewed write path |
The ARM template creates credentials under one identity in parallel unless chained with dependsOn; Learn says concurrent creates fail with 40928 |
A pipeline that fails on its second credential | The writer sends one at a time, the identity before its credentials |
The verdict. Use Learn’s samples as the model for the three writes (a credential on an app, an identity, a credential on an identity). Don’t use them as the conversion: they accept the wrong subject in silence, and two of them carry values that will never match a token.
The code: what examples/non-person-identity adds
This chapter’s code is powershell/NpiFederation/NpiFederation.psm1, one entry script, Invoke-NpiFederation.ps1 (plan by default; the write path with -PlanPath), its own call list, config/calls-ch05.json, and the fixtures in tests/fixtures/ch05. It shares NpiCore’s cloud table, host check, findings and hashes, and keeps its own client, fakes and manifest, so Chapters 01 and 02 are untouched.
The inputs
| File | Who keeps it | What it holds |
|---|---|---|
conversions.json |
Identity engineering, reviewed with the application owners | One entry per legacy credential: wave, risk, owner; the legacy account or secret; what the host allows (azureHosted, oidcIssuer); the target (a user-assigned or system-assigned managed identity, an app registration’s federated credentials, or a certificate); and for each federated credential its name, issuer, subject, audience and the workload whose issuer will present the token (github with repository, entity and value; kubernetes with issuer, namespace and service account; or a capturedSubject from a test run) |
cutover-log.csv |
The application owner | ConversionId, CutoverOn (the day the workload switched), RecordedBy, DisableApprovedBy (the owner’s approval after a clean run), DisabledOn |
The checks
New-NpiWifPlan reads, for each conversion, the app registration (GET /applications/{id}), its credentials, the user-assigned identity (a 404 means it doesn’t exist yet) and its credentials, or the system-assigned identity’s service principal; the managed identity’s Azure role assignments ($filter=assignedTo('{principalId}')); the legacy AD account; and, once a cutover is recorded, the sign-ins since then.
| Rule | Severity | What it flags | Source |
|---|---|---|---|
| NPI-WIF-01 | Finding | A target out of the plan’s order: an Azure-hosted workload given anything but a managed identity, or an OIDC-capable one given a certificate | Book 02, Chapter 10; plan NP3.2 (Design guidance) |
| NPI-WIF-02 | Finding | A subject that isn’t, exactly and case for case, what the workload’s issuer will present, or one that can’t be checked because it can’t be derived | Learn29 30 |
| NPI-WIF-03 | Finding | An issuer that isn’t exactly the workload’s (a trailing slash included), or one with whitespace | Learn31 |
| NPI-WIF-04 | Finding | Not exactly one audience, or an audience that isn’t exactly api://AzureADTokenExchange |
Learn32 |
| NPI-WIF-05 | Finding | A name outside the rules, a value over 600 characters, a wildcard, a credential on a system-assigned identity, or a flexible credential’s claimsMatchingExpression (preview; not in production, Design guidance) |
Learn33 34 35 |
| NPI-WIF-06 | Finding | More than 20 credentials on one parent, or the same issuer and subject twice | Learn36 |
| NPI-WIF-07 | Warn | A credential in the tenant that the reviewed file doesn’t hold; reported, never deleted | Design guidance |
| NPI-WIF-08 | Finding | The token exchange test failed | Project plan, risks |
| NPI-WIF-09 | Finding | A parallel run that isn’t clean: the legacy credential still in use after cutover, failures or silence on the new one, or a cutover made while a credential fails its checks | Implementation guide, Step 5 (Design guidance for the thresholds) |
What the workload presents. Get-NpiWifPresentedClaims turns the workload record into the iss, sub and aud its issuer will put in the token. For GitHub it uses Learn’s formats: repo:<org/repo>:environment:<name>, repo:<org/repo>:ref:refs/heads/<branch> and repo:<org/repo>:ref:refs/tags/<tag>, with the issuer https://token.actions.githubusercontent.com.37 It derives nothing for a pull request, whose format two Learn pages give differently, or for GitHub’s immutable subject format, which Learn says GitHub applies from July 15, 2026 to repositories created, renamed or transferred.38 Those need capturedSubject, the sub claim printed by a test run (Lab 2). For Kubernetes the subject is system:serviceaccount:<namespace>:<name>.39 Learn’s guidance on mutable subjects, to prefer a subject built from a stable ID, is why the code records which source each subject came from.40
The token exchange test. Test-NpiWifTokenExchange builds Learn’s client credentials request with a federated credential: client_id, a scope ending in /.default, client_assertion_type urn:ietf:params:oauth:client-assertion-type:jwt-bearer, the assertion, and grant_type client_credentials, addressed to {sign-in host}/{tenant}/oauth2/v2.0/token.41 The assertion carries the claims the workload presents and no valid signature. The code refuses to send it anywhere but New-NpiWifFakeTokenEndpoint, which matches it as Learn describes (issuer, subject and audience exactly and case-sensitively; whitespace around the issuer blocked; an expired assertion refused) against the parent’s credentials as they will be after the plan, and passes only if the named credential is the one that matched. It checks no signature and fetches no issuer keys: that, and the real service’s answer, are Lab 2’s.
The plan
Nothing in the plan writes. Each conversion gets one or more actions:
| Action | When | Writes? |
|---|---|---|
CreateIdentity |
A user-assigned identity the file asks for doesn’t exist: PUT {identity}?api-version=2024-11-30 with location and a conversion tag |
Yes, after review |
IdentityExists |
Recorded: the identity’s client and principal IDs, and its role assignments | No |
AwaitHost |
A system-assigned identity not yet turned on in the host’s deployment | No |
CreateCredential |
A credential that passes every check and its exchange test: POST /applications/{id}/federatedIdentityCredentials, or PUT {identity}/federatedIdentityCredentials/{name} |
Yes, after review |
NoChange, ReviewChange |
In the tenant as reviewed; or in the tenant with other values. Credentials aren’t changed in place: a new name and its own parallel run (Design guidance) | No |
Blocked |
A credential or conversion that fails a check or its exchange test | No |
Unmanaged |
In the tenant, not in the file; reported, never deleted | No |
HandOffChapter04 |
A certificate target: issued from the vault and rotated under Chapter 04 | No |
AwaitingCutover |
No cutover recorded; the conversion’s cutover gate says Ready, NotReady or Blocked, and why | No |
ParallelRunning |
Day N of 30, with the date the window ends and the new identity’s sign-ins | No |
LegacyStillInUse, NewCredentialFailing, NoSignInsOnNewCredential |
The run isn’t clean (NPI-WIF-09) | No |
AwaitingOwnerApproval |
A clean 30 days; the application owner approves the disable in the cutover log | No |
DisableLegacy |
A clean 30 days and the owner’s approval: Disable-ADAccount -Identity <objectGUID> |
Yes, after review |
RemoveSecretByOwner |
A clean 30 days for an app registration’s secret: the owner removes it in a separate reviewed change made by hand, because Chapter 04’s write path accepts only certificates; this code removes no credential | No |
Converted |
DisabledOn recorded: record the target, the date and the disabled object’s ID |
No |
The evidence for a clean run. The new identity’s sign-ins come from Graph’s beta sign-in log, filtered by time, appId and signInEventTypes (managedIdentity or servicePrincipal), because only beta returns service principal and managed identity sign-ins, and only beta carries clientCredentialType and federatedCredentialId.42 43 A new credential’s sign-in is one with clientCredentialType federatedIdentityCredential and the credential’s own ID, or managedIdentity for a managed identity. The legacy credential is in use if a clientSecret sign-in appears on the app after cutover, or the legacy AD account’s LastLogonDate is after it, which is a lead to confirm on the domain controllers, not a fact (Chapter 01’s research gaps, row 3). Design guidance: 30 days with no legacy use, no failed sign-ins on the new credential, at least one successful one, and a week with none is a finding.
The plan is written as plan.json with the conversion file’s and cutover log’s SHA-256, the tenant, the cloud and the date, and the run prints the plan’s SHA-256. The same inputs give the same plan, byte for byte. The run also writes findings.json, conversion-records.json (each conversion’s identity, role assignments, cutover gate and parallel run) and exchange-tests.json, with a manifest of every request and a SHA-256 per file.
The separate write path
Invoke-NpiFederation.ps1 -PlanPath is the only code in the chapter that writes, through a client for the federation-apply task. It refuses to run unless the plan file’s SHA-256 is the one passed as -ApprovedSha256, the plan is for this tenant and cloud, and it is no more than seven days old (Design guidance, as in Chapter 02). Each write must be a declared federation-apply call with its method and a target on the cloud’s own host; a credential’s body may hold only name, issuer, subject, audiences and description (or, on an identity, properties with the first three), and it is checked again, against the claims recorded in the plan, so a hand-edited plan can’t create a credential the plan would have blocked. DELETE is never sent. A live run is delegated: a person signed in with Cloud Application Administrator (or the application’s owner) and Managed Identity Contributor, never an unattended app.
Before each write it re-reads the target and skips the write if the identity now exists, a credential with that name or that issuer and subject appeared, the parent holds 20, or the AD account is already disabled. It sends one write at a time, in the plan’s order, so an identity exists before its credentials and no two credentials under one identity are created together.44 Without -Apply it stops after the prechecks and reports WouldSend. With -Apply it sends, records every request in its manifest, and tells the operator to record DisabledOn, and to wait for a new credential to propagate (Learn: AADSTS70021 for a few minutes) before the first real exchange.45
| Task | Calls | Permissions and roles |
|---|---|---|
federation (read) |
GET /applications/{id}, its credentials, GET /servicePrincipals/{id}; GET /beta/auditLogs/signIns; GET user-assigned identity, its credentials, role assignments; Get-ADUser; the token exchange test (fake only) |
Application.Read.All, AuditLog.Read.All; Reader; default AD read46 47 48 |
federation-apply (write, delegated only) |
Re-read credentials and identities; POST application credential; PUT identity; PUT identity credential; Get-ADUser, Disable-ADAccount |
Application.Read.All, Application.ReadWrite.All; Managed Identity Contributor, Reader; a delegated right to disable accounts in the account’s OU49 50 |
Application.ReadWrite.All is the only delegated permission Learn lists for creating a federated credential on an application; the application permission Application.ReadWrite.OwnedBy reaches only apps the caller owns, and a person needs Cloud Application Administrator or Application Administrator, or, for an app they own, Application Developer or a member’s default permissions.51 Learn’s trust page names Contributor or Owner on the identity for creating its credential; the Managed Identity Contributor built-in role holds the same federatedIdentityCredentials/write action and is what Learn names for creating the identity, so the call list declares it (research gaps, row 45).52 53 54
The tests
Pester 5 under PowerShell 7. This chapter’s are:
tests/NpiFederationSamples.Tests.ps1, 17 tests: Microsoft’s samples, run as published.tests/NpiFederation.Tests.ps1, 38 tests: the call list and its least-privilege check, the client and the token exchange harness, the claims and credential checks, the order, each finding and plan action, the 20-credential limit, the deterministic plan hash, each parallel run status, the writer’s refusals (hash, tenant, cloud, age, task, host, body,DELETE, a credential the checks would block), dry run,-Applyin order, the prechecks that skip, and the entry script and its manifest.
The commands below send Microsoft’s pinned samples to fakes, and run the example against a fictional tenant. -Apply in Lab 1 changes only the fake, in memory. The token exchange test proves a credential will match the claims you recorded; it doesn’t prove the workload’s issuer presents them. Review the code, and run Lab 2 in a test tenant, before it reads or writes a real one.
Lab 1: fixtures, no tenant
You need PowerShell 7.1 or later with Pester 5. Work in examples/non-person-identity.
Every command in this lab, in order, from examples/non-person-identity. Each step below says what it does and what to look for.
# Step 1: Send Microsoft's samples to the fakes
Invoke-Pester -Path tests/NpiFederationSamples.Tests.ps1 -Output None -PassThru |
Select-Object PassedCount, FailedCount, NotRunCount
# Step 2: List what each task needs
Import-Module ./powershell/NpiFederation/NpiFederation.psm1
foreach ($t in 'federation', 'federation-apply') { $p = Get-NpiWifRequiredPermission -Task $t; "$t : " + ($p.GraphPermissions -join ', ') + ' | ' + ($p.AzureRoles -join ', ') }
# Step 3: Build the plan
./Invoke-NpiFederation.ps1 -Cloud gcc -ConversionsPath tests/fixtures/ch05/conversions.json -CutoverLogPath tests/fixtures/ch05/cutover-log.csv -FixturePath tests/fixtures/ch05/tenant.json -OutputPath out/federation
# Step 4: Read the findings
Get-Content out/federation/findings.json -Raw | ConvertFrom-Json | Format-Table Rule, Severity, ObjectKey
# Step 5: Read the exchange tests
Get-Content out/federation/exchange-tests.json -Raw | ConvertFrom-Json | Format-Table Credential, SubjectSource, Passed
# Step 6: Read the conversions
Get-Content out/federation/conversion-records.json -Raw | ConvertFrom-Json | ForEach-Object { [pscustomobject]@{ Id = $_.ConversionId; Target = $_.TargetKind; Gate = $_.CutoverGate; Run = $_.ParallelRun.Status; Day = $_.ParallelRun.Day } } | Format-Table
# Step 7: Dry-run the plan with its approved hash
$sha = (Get-FileHash out/federation/plan.json -Algorithm SHA256).Hash.ToLower()
./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation/plan.json -ApprovedSha256 $sha -FixturePath tests/fixtures/ch05/tenant.json -OutputPath out/federation-dryrun
# Step 8: Apply it to the fake, then try a hash nobody approved
./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation/plan.json -ApprovedSha256 $sha -FixturePath tests/fixtures/ch05/tenant.json -OutputPath out/federation-apply -Apply
./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation/plan.json -ApprovedSha256 ('0' * 64) -FixturePath tests/fixtures/ch05/tenant.json -OutputPath out/federation-x
# Step 9: Run the tests
Invoke-Pester -Path tests
# After the steps
Remove-Item -Recurse -Force outSend Microsoft’s samples to the fakes:
Invoke-Pester -Path tests/NpiFederationSamples.Tests.ps1 -Output None -PassThru | Select-Object PassedCount, FailedCount, NotRunCountWhat the terminal shows, run on the kit’s fixtures (the same output as text) PassedCount 17,FailedCount 0,NotRunCount 0. Read the test that sends the Graph sample and then exchanges a token with another subject: the create succeeds and the exchange fails withAADSTS70021.List what each task needs:
Import-Module ./powershell/NpiFederation/NpiFederation.psm1 foreach ($t in 'federation', 'federation-apply') { $p = Get-NpiWifRequiredPermission -Task $t; "$t : " + ($p.GraphPermissions -join ', ') + ' | ' + ($p.AzureRoles -join ', ') }federation : Application.Read.All, AuditLog.Read.All | Reader federation-apply : Application.Read.All, Application.ReadWrite.All | Managed Identity Contributor, ReaderWhat the terminal shows, run on the kit’s fixtures (the same output as text) Build the plan:
./Invoke-NpiFederation.ps1 -Cloud gcc -ConversionsPath tests/fixtures/ch05/conversions.json -CutoverLogPath tests/fixtures/ch05/cutover-log.csv -FixturePath tests/fixtures/ch05/tenant.json -OutputPath out/federationConversions: 8 in 2 wave(s); token exchange tests: 4 passed, 2 failed Findings: 7 Finding, 1 Warn Plan: 20 actions, 4 of them writes (2 CreateCredential, 1 CreateIdentity, 1 DisableLegacy); nothing sent Parallel runs: conv-01 ReadyToDisableLegacy; conv-02 AwaitingCutover; conv-03 AwaitingCutover; conv-04 AwaitingCutover; conv-05 AwaitingCutover; conv-06 AwaitingCutover; conv-07 ParallelRunning; conv-08 LegacyStillInUse Plan SHA-256: 5ec0a52c70dc49ac6904c6923ac9b292e3c312d38594b6210cc9178c9f188bf2What the terminal shows, run on the kit’s fixtures (the same output as text) The plan’s hash is the same on every run with these inputs.
Read the findings:
Get-Content out/federation/findings.json -Raw | ConvertFrom-Json | Format-Table Rule, Severity, ObjectKeyRule Severity ObjectKey ---- -------- --------- NPI-WIF-03 Finding conv-02/payroll-prod NPI-WIF-02 Finding conv-02/payroll-prod NPI-WIF-08 Finding conv-02/payroll-prod NPI-WIF-07 Warn conv-02/payroll-test NPI-WIF-04 Finding conv-03/github-intranet-tag NPI-WIF-08 Finding conv-03/github-intranet-tag NPI-WIF-01 Finding conv-06 NPI-WIF-09 Finding conv-08What the terminal shows, run on the kit’s fixtures (the same output as text) payroll-prodcopies the Azure PowerShell sample’s issuer, with its trailing slash, and names the environmentProductionwhere the workflow’s isproduction: two findings, and an exchange test that fails.payroll-testwas added in the portal and isn’t in the file.github-intranet-tagcopies the ARM template’s audience, leading space and all.conv-06gives an Azure Functions app a certificate when a managed identity is available.conv-08’s secret still signed in on October 28, eight weeks after cutover.Read the exchange tests:
Get-Content out/federation/exchange-tests.json -Raw | ConvertFrom-Json | Format-Table Credential, SubjectSource, PassedCredential SubjectSource Passed ---------- ------------- ------ payroll-prod derived False github-main derived True github-intranet derived True github-intranet-tag derived False aks-webfront derived True gh-reports derived TrueWhat the terminal shows, run on the kit’s fixtures (the same output as text) aks-webfrontis on an identity the plan will create: its test ran against a client ID markedpending:, and its real exchange waits for Lab 2.Read the conversions:
Get-Content out/federation/conversion-records.json -Raw | ConvertFrom-Json | ForEach-Object { [pscustomobject]@{ Id = $_.ConversionId; Target = $_.TargetKind; Gate = $_.CutoverGate; Run = $_.ParallelRun.Status; Day = $_.ParallelRun.Day } } | Format-TableId Target Gate Run Day -- ------ ---- --- --- conv-01 managedIdentity Ready ReadyToDisableLegacy 40 conv-02 federatedCredential Blocked AwaitingCutover conv-03 federatedCredential Blocked AwaitingCutover conv-04 managedIdentity NotReady AwaitingCutover conv-05 certificate NotReady AwaitingCutover conv-06 certificate Blocked AwaitingCutover conv-07 managedIdentity Ready ParallelRunning 15 conv-08 federatedCredential Ready LegacyStillInUse 59What the terminal shows, run on the kit’s fixtures (the same output as text) conv-01(the fixture’ssvc-hrsyncto the user-assigned identityid-hrsync) has run 40 days with the identity signing in and no logon by the old account since cutover, and its owner approved: the one disable in the plan.conv-07’s window ends November 14.conv-03is blocked by its tag credential, thoughgithub-intranetitself is created.conv-05, an on-premises task with no OIDC issuer, goes to a certificate under Chapter 04.The fixture uses
svc-hrsynconly to exercise the parallel run. The program’s target forsvc-hrsyncis a dMSA, as Chapter 03’s matrix proposes and Chapter 06 migrates it; don’t convert it to a managed identity.Dry-run the plan with its approved hash:
$sha = (Get-FileHash out/federation/plan.json -Algorithm SHA256).Hash.ToLower() ./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation/plan.json -ApprovedSha256 $sha -FixturePath tests/fixtures/ch05/tenant.json -OutputPath out/federation-dryrunDryRun: 4 WouldSend WouldSend DisableLegacy WRITE Disable-ADAccount -Identity ad000000-0000-4000-8000-000000000001 WouldSend CreateCredential POST https://graph.microsoft.com/v1.0/applications/0a000000-0000-4000-8000-000000000004/federatedIdentityCredentials WouldSend CreateIdentity PUT https://management.azure.com/subscriptions/11111111-1111-4111-8111-111111111111/resourceGroups/rg-identity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-webfront?api-version=2024-11-30 WouldSend CreateCredential PUT https://management.azure.com/subscriptions/11111111-1111-4111-8111-111111111111/resourceGroups/rg-identity/providers/Microsoft.ManagedIdentity/userAssignedIdentities/id-webfront/federatedIdentityCredentials/aks-webfront?api-version=2024-11-30What the terminal shows, run on the kit’s fixtures (the same output as text) Apply it to the fake, then try a hash nobody approved:
./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation/plan.json -ApprovedSha256 $sha -FixturePath tests/fixtures/ch05/tenant.json -OutputPath out/federation-apply -Apply ./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation/plan.json -ApprovedSha256 ('0' * 64) -FixturePath tests/fixtures/ch05/tenant.json -OutputPath out/federation-xWhat the terminal shows, run on the kit’s fixtures (the same output as text) Apply: 4 Sent, in the same order, the identity before its credential; then the refusal,The plan's SHA-256 is 5ec0a52c…, not the approved 0000…; refusing to run a plan nobody reviewed.Run the tests:
Invoke-Pester -Path testsWhat the terminal shows, run on the kit’s fixtures (the same output as text)
Expected result: 513 tests passing across the example, out/federation/manifest.json listing 20 reads (5 application reads, 4 credential lists, 1 service principal, 4 sign-in pages, 2 identities, 2 role assignment reads, 2 AD reads) and 6 token exchange tests sent to the fake, and out/federation-apply/manifest.json listing the prechecks and the four writes. Delete out when you’re done:
Remove-Item -Recurse -Force outLab 2: test tenant
Use a test tenant in the worldwide or GCC cloud, a test subscription, a test Active Directory domain and a test repository, never production, with fictional names. Work in examples/non-person-identity on a domain-joined test host. You’ll replace: <tenant-id> (the test tenant’s ID), <app-id> and <thumbprint> (an app holding only Application.Read.All and AuditLog.Read.All with Reader on the test subscription, and its certificate), <test-domain> (the test domain’s DNS name), <conversions> (the path of your conversions.json), <cutover-log> (the path of your cutover log), <test-app-object-id> and <test-app-client-id> (the test app registration’s Object ID and Application (client) ID), <owner>/<repo> (the test repository), <account-conversion-id> (the id of the conversion entry for the test account and its new identity), <your-upn> (your user principal name), <owner-upn> (the test account’s owner), <test-account> (the test account’s sAMAccountName), and <sha>, <sha2> (each plan’s SHA-256, as the reviewer passes it to you).
Every command in this lab, in order, from examples/non-person-identity. Each step below says what it does and what to look for.
# Step 1: Make a test conversion
Get-Content tests/fixtures/ch05/cutover-log.csv -TotalCount 1 | Set-Content <cutover-log>
# Step 3: Plan
Connect-MgGraph -Environment Global -TenantId <tenant-id> -ClientId <app-id> -CertificateThumbprint <thumbprint>
Connect-AzAccount -ServicePrincipal -Environment AzureCloud -Tenant <tenant-id> -ApplicationId <app-id> -CertificateThumbprint <thumbprint>
./Invoke-NpiFederation.ps1 -Cloud gcc -TenantId <tenant-id> -Domain <test-domain> -ConversionsPath <conversions> -CutoverLogPath <cutover-log> -OutputPath out/federation-test
# Step 3: Plan
(Get-FileHash out/federation-test/plan.json -Algorithm SHA256).Hash.ToLower()
# Step 4: Dry-run, then apply
Connect-MgGraph -Environment Global -TenantId <tenant-id> -Scopes Application.Read.All, Application.ReadWrite.All
Connect-AzAccount -Environment AzureCloud -Tenant <tenant-id>
./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation-test/plan.json -ApprovedSha256 <sha> -Domain <test-domain> -OutputPath out/federation-test-dry
./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation-test/plan.json -ApprovedSha256 <sha> -Domain <test-domain> -OutputPath out/federation-test-apply -Apply
# Step 5: Exchange for real
Connect-MgGraph -Environment Global -TenantId <tenant-id> -ClientId <app-id> -CertificateThumbprint <thumbprint>
(Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=appId eq '<test-app-client-id>' and signInEventTypes/any(t: t eq 'servicePrincipal')").value | ConvertTo-Json -Depth 5
# Step 5: Exchange for real
Connect-MgGraph -Environment Global -TenantId <tenant-id> -Scopes Application.Read.All, Application.ReadWrite.All
Invoke-MgGraphRequest -Method POST -Uri https://graph.microsoft.com/v1.0/applications/<test-app-object-id>/federatedIdentityCredentials -Body @{ name = 'npi-test-wrong-subject'; issuer = 'https://token.actions.githubusercontent.com'; subject = 'repo:<owner>/<repo>:environment:wrong'; audiences = @('api://AzureADTokenExchange') }
# Step 6: Run the parallel run
Connect-MgGraph -Environment Global -TenantId <tenant-id> -ClientId <app-id> -CertificateThumbprint <thumbprint>
Connect-AzAccount -ServicePrincipal -Environment AzureCloud -Tenant <tenant-id> -ApplicationId <app-id> -CertificateThumbprint <thumbprint>
Add-Content <cutover-log> "<account-conversion-id>,$(Get-Date -Format yyyy-MM-dd),<your-upn>,,"
./Invoke-NpiFederation.ps1 -Cloud gcc -TenantId <tenant-id> -Domain <test-domain> -ConversionsPath <conversions> -CutoverLogPath <cutover-log> -OutputPath out/federation-test-cutover
Get-Content out/federation-test-cutover/conversion-records.json -Raw | ConvertFrom-Json | ForEach-Object { [pscustomobject]@{ Id = $_.ConversionId; Run = $_.ParallelRun.Status; Day = $_.ParallelRun.Day } } | Format-Table
# Step 6: Run the parallel run
$log = Import-Csv <cutover-log>
($log | Where-Object ConversionId -eq '<account-conversion-id>').DisableApprovedBy = '<owner-upn>'
$log | Export-Csv <cutover-log> -NoTypeInformation -UseQuotes AsNeeded
$asOf = (Get-Date).AddDays(31)
./Invoke-NpiFederation.ps1 -Cloud gcc -TenantId <tenant-id> -Domain <test-domain> -ConversionsPath <conversions> -CutoverLogPath <cutover-log> -AsOf $asOf -OutputPath out/federation-test-day31
# Step 6: Run the parallel run
Connect-MgGraph -Environment Global -TenantId <tenant-id> -Scopes Application.Read.All, Application.ReadWrite.All
Connect-AzAccount -Environment AzureCloud -Tenant <tenant-id>
./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation-test-day31/plan.json -ApprovedSha256 <sha2> -Domain <test-domain> -AsOf $asOf -OutputPath out/federation-test-day31-apply -Apply
Get-ADUser -Identity <test-account> | Select-Object SamAccountName, Enabled
# Step 7: Back it out
Enable-ADAccount -Identity <test-account>
Remove-Item -Recurse -Force out/federation-test*Make a test conversion. A test app registration with a client secret, an enabled test account (
svc-prefix) in a test OU, and a test repository whose workflow signs in with Learn’s Azure Login sample (pinned inexamples/third_party/azure-login-oidc-sample). Write a two-entryconversions.json: the test app getting a GitHub federated credential for the repository’stestenvironment, and a new test user-assigned identity with no credential.tests/fixtures/ch05/conversions.jsonshows each field:conv-02is a federated credential on an application, andconv-04a new user-assigned identity. Then make the cutover log with the fixture’s header:Get-Content tests/fixtures/ch05/cutover-log.csv -TotalCount 1 | Set-Content <cutover-log>Capture the claims. Run the workflow once with a step that prints the decoded
issandsubof its OIDC token, never the token itself. Record them ascapturedSubjectandcapturedIssuer, and record whether theissends with a slash (research gaps, row 41) and whether the repository uses the immutable subject format (row 43).Plan with an app holding only
Application.Read.AllandAuditLog.Read.Alland Reader on the test subscription, signed in as in Chapter 01:Connect-MgGraph -Environment Global -TenantId <tenant-id> -ClientId <app-id> -CertificateThumbprint <thumbprint> Connect-AzAccount -ServicePrincipal -Environment AzureCloud -Tenant <tenant-id> -ApplicationId <app-id> -CertificateThumbprint <thumbprint> ./Invoke-NpiFederation.ps1 -Cloud gcc -TenantId <tenant-id> -Domain <test-domain> -ConversionsPath <conversions> -CutoverLogPath <cutover-log> -OutputPath out/federation-testHave a second person review
plan.jsonand pass you its SHA-256. They compute it with:(Get-FileHash out/federation-test/plan.json -Algorithm SHA256).Hash.ToLower()Dry-run, then apply, signed in as a person with Cloud Application Administrator and Managed Identity Contributor on the test resource group:
Connect-MgGraph -Environment Global -TenantId <tenant-id> -Scopes Application.Read.All, Application.ReadWrite.All Connect-AzAccount -Environment AzureCloud -Tenant <tenant-id> ./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation-test/plan.json -ApprovedSha256 <sha> -Domain <test-domain> -OutputPath out/federation-test-dry ./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation-test/plan.json -ApprovedSha256 <sha> -Domain <test-domain> -OutputPath out/federation-test-apply -ApplyExchange for real. Wait a few minutes for the credential to propagate,55 run the workflow, and confirm its sign-in appears in the beta sign-in log with
clientCredentialTypefederatedIdentityCredentialand the credential’s ID. Then add a second test credential with a deliberately wrong subject, run the workflow against it, and record the error the token endpoint returns (research gaps, row 44). Record whether a sign-in with the old client secret shows its key ID (row 47). The first commands read the app’s service principal sign-ins with step 3’s app, using the filter the code sends:Connect-MgGraph -Environment Global -TenantId <tenant-id> -ClientId <app-id> -CertificateThumbprint <thumbprint> (Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/beta/auditLogs/signIns?`$filter=appId eq '<test-app-client-id>' and signInEventTypes/any(t: t eq 'servicePrincipal')").value | ConvertTo-Json -Depth 5The second credential, sent as Learn’s create request, signed in as in step 4; its subject names an environment the workflow doesn’t use:
Connect-MgGraph -Environment Global -TenantId <tenant-id> -Scopes Application.Read.All, Application.ReadWrite.All Invoke-MgGraphRequest -Method POST -Uri https://graph.microsoft.com/v1.0/applications/<test-app-object-id>/federatedIdentityCredentials -Body @{ name = 'npi-test-wrong-subject'; issuer = 'https://token.actions.githubusercontent.com'; subject = 'repo:<owner>/<repo>:environment:wrong'; audiences = @('api://AzureADTokenExchange') }Run the parallel run. Record
CutoverOnin the cutover log, plan again, and confirmParallelRunningwith one sign-in. To see the disable without waiting 30 days, run the plan with-AsOf31 days later and the owner’s approval recorded, review, and apply: the test account is disabled. Signed in as in step 3, record today’s cutover and plan:Connect-MgGraph -Environment Global -TenantId <tenant-id> -ClientId <app-id> -CertificateThumbprint <thumbprint> Connect-AzAccount -ServicePrincipal -Environment AzureCloud -Tenant <tenant-id> -ApplicationId <app-id> -CertificateThumbprint <thumbprint> Add-Content <cutover-log> "<account-conversion-id>,$(Get-Date -Format yyyy-MM-dd),<your-upn>,," ./Invoke-NpiFederation.ps1 -Cloud gcc -TenantId <tenant-id> -Domain <test-domain> -ConversionsPath <conversions> -CutoverLogPath <cutover-log> -OutputPath out/federation-test-cutover Get-Content out/federation-test-cutover/conversion-records.json -Raw | ConvertFrom-Json | ForEach-Object { [pscustomobject]@{ Id = $_.ConversionId; Run = $_.ParallelRun.Status; Day = $_.ParallelRun.Day } } | Format-TableThen record the owner’s approval and plan as of 31 days from now:
$log = Import-Csv <cutover-log> ($log | Where-Object ConversionId -eq '<account-conversion-id>').DisableApprovedBy = '<owner-upn>' $log | Export-Csv <cutover-log> -NoTypeInformation -UseQuotes AsNeeded $asOf = (Get-Date).AddDays(31) ./Invoke-NpiFederation.ps1 -Cloud gcc -TenantId <tenant-id> -Domain <test-domain> -ConversionsPath <conversions> -CutoverLogPath <cutover-log> -AsOf $asOf -OutputPath out/federation-test-day31The reviewer passes you
out/federation-test-day31/plan.json’s SHA-256 as<sha2>. Signed in as in step 4, in the same session:Connect-MgGraph -Environment Global -TenantId <tenant-id> -Scopes Application.Read.All, Application.ReadWrite.All Connect-AzAccount -Environment AzureCloud -Tenant <tenant-id> ./Invoke-NpiFederation.ps1 -Cloud gcc -PlanPath out/federation-test-day31/plan.json -ApprovedSha256 <sha2> -Domain <test-domain> -AsOf $asOf -OutputPath out/federation-test-day31-apply -Apply Get-ADUser -Identity <test-account> | Select-Object SamAccountName, EnabledBack it out. Enable the test account, remove the test credentials and delete the test identity, the test app registration and the test account by hand in the test tenant and domain (this code never deletes), and delete the
outfolders: they name accounts and identities. The commands for the first and the last:Enable-ADAccount -Identity <test-account> Remove-Item -Recurse -Force out/federation-test*
Verified facts
| Item | Value | US Government clouds | Stage | Source |
|---|---|---|---|---|
| Create a federated credential on an application | POST /applications/{id}/federatedIdentityCredentials; audiences one value, api://AzureADTokenExchange for Entra ID; name up to 120 characters, immutable; issuer and subject unique on the app; maximum 20; delegated Application.ReadWrite.All, application Application.ReadWrite.OwnedBy; Cloud Application Administrator or Application Administrator, or Application Developer or a member for apps they own |
L4 and L5 | v1.0 | 56 |
| List an application’s federated credentials | Application.Read.All (the Get page differs; Chapter 01’s row 4) |
L4 and L5 | v1.0 | 57 |
| Credential rules | Issuer, subject and audience must match the token; issuer whitespace blocks the exchange; 600 characters; one audience, api://AzureADTokenExchange recommended; name 3–120, alphanumeric first; no wildcards; 20 per app or user-assigned identity; a wrong subject is created without error |
— | GA | 58 |
| Case | Issuer, subject and audience match case-sensitively | — | GA | 59 |
| System-assigned identity | Can’t hold a federated credential | — | — | 60 |
| Create a federated credential on a user-assigned identity | PUT {identity}/federatedIdentityCredentials/{name}?api-version=2024-11-30; name pattern ^[a-zA-Z0-9]{1}[a-zA-Z0-9-_]{2,119}$; Contributor or Owner on the trust page |
Not stated on the pages read | GA (2024-11-30) | 61 62 |
| Concurrency and propagation | Credentials under one identity created concurrently fail with 409; create them in sequence; a token request minutes after creating one may fail with AADSTS70021; retry |
— | — | 63 |
| Create a user-assigned identity | PUT {identity}?api-version=2024-11-30 with location; Managed Identity Contributor; reading needs Managed Identity Operator or Contributor |
Not stated on the pages read | GA | 64 65 |
| Managed Identity Contributor | Includes userAssignedIdentities/write and federatedIdentityCredentials/write |
— | GA | 66 |
| List role assignments | GET {scope}/providers/Microsoft.Authorization/roleAssignments?api-version=2022-04-01&$filter=assignedTo('{objectId}'); roleAssignments/read, such as Reader |
— | GA | 67 |
| Token request with a federated credential | Client credentials grant; client_assertion_type urn:ietf:params:oauth:client-assertion-type:jwt-bearer; the assertion comes from the external issuer |
— | GA | 68 |
| Sign-in log for service principals and managed identities | GET /beta/auditLogs/signIns with a signInEventTypes filter (only interactive sign-ins otherwise); AuditLog.Read.All; Reports Reader and others for delegated access; P1 or P2 license |
L4 and L5 | Beta | 69 70 |
| Sign-in properties | clientCredentialType (clientSecret, federatedIdentityCredential, managedIdentity, certificate, …), federatedCredentialId, servicePrincipalCredentialKeyId |
— | Beta | 71 |
| Flexible federated credentials | claimsMatchingExpression instead of subject; Graph beta for apps; Resource Manager preview API versions for identities |
— | Preview | 72 |
| GitHub immutable subjects | Applied automatically from July 15, 2026 to repositories created, renamed or transferred | — | — | 73 74 |
Where Learn’s pages differ: the GitHub issuer has no trailing slash in the CLI and REST create samples and on the flexible credential page, and has one in the Azure PowerShell sample, the CLI page’s prose and the REST list response (research gaps, row 41); the pull request subject is :pull-request on the Entra page and :pull_request on App Service’s (row 42); the trust page names Contributor or Owner for a credential on an identity, while Managed Identity Contributor holds the action and is what Learn names for creating the identity (row 45); and the how-to samples use API versions 2022-01-31-preview and 2015-08-31-preview where the reference is 2024-11-30 (row 48). The code follows the stricter reading in each case: an exact comparison against the claim captured from a real token, no derived pull request subject, the narrower role, and the GA API version.
Federal requirements and evidence
The conversion is what the guide’s G5 Conversion gate checks: each new identity built in the plan’s order, its federated credential tested by a real exchange, a clean 30-day parallel run, and the legacy account disabled, not deleted. Each row names the file or command that is the evidence. AC, AU, CM, IA-5 and SI controls are in the NIST SP 800-53 Rev. 5 Moderate baseline; IA-9 is in no baseline and is tailored in, as the guide does.75
| What the output proves | Requirement it supports | KSI it aligns to | Who keeps it |
|---|---|---|---|
Microsoft’s federation and managed identity samples, run as published, are the models the code keeps, and each gap is pinned by a test (Lab 1, step 1: NpiFederationSamples.Tests.ps1, 17 tests) |
IA-5 Authenticator Management | KSI-IAM-SNU | The repository’s tests; the pinned copies |
The plan reads with Application.Read.All, AuditLog.Read.All and Reader; the write task is separate and delegated (Lab 1, step 2, and Lab 2, step 3: the permissions for federation and federation-apply) |
AC-6 Least Privilege; CM-5 Access Restrictions for Change | KSI-IAM-ELP, KSI-IAM-JIT (AC-6); KSI-CMT-RVP (CM-5) | Identity engineering; the ISSO keeps the consent record |
Each conversion gets its actions in order: an identity before its credentials, nothing in the tenant changed in place, nothing deleted (Lab 1, step 3: plan.json; conversions.json) |
IA-5; CM-3 Configuration Change Control | KSI-IAM-SNU; KSI-CMT-RMV | Identity engineering, reviewed with application owners |
A target out of order, a subject, issuer or audience that isn’t exactly what the workload presents, too many credentials, a failed exchange or a run that isn’t clean: each is a finding (Lab 1, step 4: findings.json, NPI-WIF-01 to -09) |
IA-5 and IA-5(2); IA-9 Service Identification and Authentication | KSI-IAM-SNU (IA-5); none cites IA-9 | Identity engineering |
Each federated credential passed a token exchange test before it was planned (Lab 1, step 5: exchange-tests.json) |
IA-9; CM-4(2) Verification of Controls | KSI-CMT-VTD, KSI-CMT-LMC (CM-4(2)) | Identity engineering |
Each conversion’s identity, role assignments, cutover gate and parallel run status are recorded (Lab 1, step 6, and Lab 2, step 6: conversion-records.json; cutover-log.csv) |
AC-2 Account Management; CM-3 | KSI-IAM-SNU; KSI-CMT-LMC | Application owners; identity engineering |
The write path stops after its prechecks without -Apply, and refuses a plan whose hash nobody approved (Lab 1, steps 7 and 8: results.json in the dry-run folder; the refusal text) |
CM-3; CM-5 | KSI-CMT-RVP; KSI-IAM-JIT | The reviewer; the ISSO |
The approved identity, credentials and disable were sent one at a time, after fresh prechecks, and recorded (Lab 1, step 8, and Lab 2, step 4: results.json and manifest.json in the apply folder) |
AC-2(3) Disable Accounts; CM-3 | KSI-IAM-AAM; KSI-CMT-LMC | The ISSO, with the run’s folder |
Every run records the tenant, the cloud, every request and exchange test, and a SHA-256 per file (manifest.json in each output folder) |
AU-9 Protection of Audit Information; SI-7 Software, Firmware, and Information Integrity | KSI-MLA-OSM (AU-9); KSI-SVC-VRI (SI-7) | The ISSO |
The code passes its tests (Lab 1, step 9: Invoke-Pester -Path tests, 513 passed) |
CM-3(2) Testing, Validation, and Documentation of Changes | KSI-CMT-VTD | The repository; the pull request’s checks |
The subject and issuer in the reviewed file were captured from a real token, never derived (Lab 2, step 2: capturedSubject and capturedIssuer in conversions.json) |
IA-5 | KSI-IAM-SNU | Identity engineering |
The workload signed in with the federated credential and no secret (Lab 2, step 5: the beta sign-in log entry with clientCredentialType federatedIdentityCredential and the credential’s ID) |
IA-9; AU-2 Event Logging; AU-12 Audit Record Generation | KSI-MLA-LET (AU-2, AU-12) | Security operations |
The legacy account was disabled only after a clean 30 days and the owner’s approval (Lab 2, step 6: ParallelRunning, then DisableLegacy in plan.json; DisableApprovedBy and DisabledOn in cutover-log.csv) |
AC-2(3) | KSI-IAM-AAM | Application owners |
| The lab’s changes were reversed by a person, and this code deleted nothing (Lab 2, step 7: the account re-enabled and test objects removed by hand) | CM-3 | KSI-CMT-LMC | Identity engineering |
KSIs are FedRAMP 20x indicators for cloud service providers. The KSI column names the indicators whose related controls, as FedRAMP lists them, include the row’s control or one of its enhancements; which output supports which indicator is the program’s mapping (design guidance).76 The agency authorizes its own system and lines this evidence up with what its providers report against KSIs; it doesn’t report KSIs itself (Book 00, Appendix B). Lab steps that only make test objects or delete out have no row. The track-wide mapping is the guide’s Evidence table.
Dangers
| Danger | What happens | The guard |
|---|---|---|
| A wrong subject | Accepted at creation; the workload fails at cutover, with no error from Entra ID | Checked against what the workload presents, then the exchange test; a cutover is blocked until both pass (NPI-WIF-02, -08) |
| A trailing slash or a stray space | The issuer or audience never matches | Exact comparison; exactly one audience, exactly api://AzureADTokenExchange (NPI-WIF-03, -04) |
| A subject derived from a guess | A pull request or immutable-format subject that GitHub never presents | Derived only for Learn’s three name-based formats; otherwise captured from a test run |
| A mutable subject | A renamed repository’s name passes to someone else’s | The subject’s source is recorded; prefer the immutable format where the issuer offers it77 |
| A credential changed in place | The live workload’s exchange breaks mid-run | ReviewChange: a new name and its own parallel run |
| A credential added outside the pipeline | A trust nobody reviewed | Unmanaged (NPI-WIF-07), reported every run, never deleted by code |
| Credentials created in parallel | 409 under one identity | One write at a time, the identity first |
| The first exchange right after creation | AADSTS70021 during propagation |
The apply says to wait; Lab 2 waits and retries |
| A disable on a stale lead | The job fails because the old account was still in use | 30 days, sign-ins on the new identity, no legacy use, the owner’s approval, and a re-read before the write |
| A delete | The rollback is gone | No delete action; the writer refuses DELETE; secrets are removed by the owner in a separate reviewed change made by hand (Chapter 04’s write path accepts only certificates) |
| A secret removed too early | The workload falls back and fails | RemoveSecretByOwner only after a clean run; clientSecret sign-ins after cutover keep the run open |
| An assertion sent to a real endpoint | A token, or a test assertion, leaves the machine | The exchange test runs only against the fake; the manifest records the call, never the assertion |
An unattended app with Application.ReadWrite.All |
A standing permission that can add a credential to any app | The write path is a person’s delegated session; the read path holds Application.Read.All and AuditLog.Read.All |
| A plan changed after review | Different writes from the ones approved | The approved SHA-256; this tenant and cloud; seven days; each credential re-checked before it’s sent |
| A preview feature in production | Behavior that changes under you | Flexible credentials refused (NPI-WIF-05); the beta sign-in read labeled and its evidence kept |
Checklist
Footnotes
Microsoft Learn, “Workload identity federation concepts,” how it works (the case-sensitive match), https://learn.microsoft.com/entra/workload-id/workload-identity-federation (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Create federatedIdentityCredential,” permissions, request body, national cloud deployments, https://learn.microsoft.com/graph/api/application-post-federatedidentitycredentials?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Workload identity federation concepts,” how it works (the case-sensitive match), https://learn.microsoft.com/entra/workload-id/workload-identity-federation (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Configure a user-assigned managed identity to trust an external identity provider,” introduction, prerequisites, ARM template and REST samples, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Workload identity federation concepts,” how it works (the case-sensitive match), https://learn.microsoft.com/entra/workload-id/workload-identity-federation (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Manage user-assigned managed identities using REST,” create and list, https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/manage-user-assigned-managed-identities-rest (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Microsoft Graph national cloud deployments,” https://learn.microsoft.com/graph/deployments (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Compare Azure Government and global Azure,” guidance for developers (Azure Resource Manager endpoints), https://learn.microsoft.com/azure/azure-government/compare-azure-government-global-azure (accessed Oct 31, 2026).↩︎
Microsoft Learn, “Create federatedIdentityCredential,” permissions, request body, national cloud deployments, https://learn.microsoft.com/graph/api/application-post-federatedidentitycredentials?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎
Microsoft Learn, “List signIns” (beta), permissions, national cloud deployments, the
signInEventTypesnote, https://learn.microsoft.com/graph/api/signin-list?view=graph-rest-beta (accessed Oct 30, 2026).↩︎Microsoft Learn, “Configure an app to trust an external identity provider,” Azure CLI, Azure PowerShell and Microsoft Graph samples, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure a user-assigned managed identity to trust an external identity provider,” introduction, prerequisites, ARM template and REST samples, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Create federatedIdentityCredential,” permissions, request body, national cloud deployments, https://learn.microsoft.com/graph/api/application-post-federatedidentitycredentials?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Federated Identity Credentials - Create Or Update” (Managed Identity REST, 2024-11-30), https://learn.microsoft.com/rest/api/managedidentity/federated-identity-credentials/create-or-update (accessed Oct 30, 2026).↩︎
Microsoft Learn, “User Assigned Identities - Create Or Update” (Managed Identity REST, 2024-11-30), https://learn.microsoft.com/rest/api/managedidentity/user-assigned-identities/create-or-update (accessed Oct 30, 2026).↩︎
Microsoft, MicrosoftDocs/entra-docs,
docs/workload-id/workload-identity-federation-create-trust.md,docs/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity.mdanddocs/workload-id/includes/federated-credential-configuration-considerations.mdat commitd75a90ea1b07d0d2007cfab256c1ad02653d1e35(February 25, 2026), code MIT License, documentation Creative Commons Attribution 4.0, https://github.com/MicrosoftDocs/entra-docs/tree/d75a90ea1b07d0d2007cfab256c1ad02653d1e35/docs/workload-id (accessed Oct 30, 2026); pinned copy inexamples/third_party/entra-wif-create-trust-pages.↩︎Microsoft, microsoftgraph/microsoft-graph-docs-contrib,
api-reference/v1.0/api/federatedidentitycredential-post.mdat commit08b242e8a85a03db4c4d1f41564cfc81632d5cd1(December 4, 2025), code MIT License, documentation Creative Commons Attribution 4.0, https://github.com/microsoftgraph/microsoft-graph-docs-contrib/blob/08b242e8a85a03db4c4d1f41564cfc81632d5cd1/api-reference/v1.0/api/federatedidentitycredential-post.md (accessed Oct 30, 2026); pinned copy inexamples/third_party/graph-fic-create-page.↩︎Microsoft, Azure/azure-rest-api-specs,
specification/msi/resource-manager/Microsoft.ManagedIdentity/ManagedIdentity/stable/2024-11-30/examples(three files) at commit37886acc4980d7ac8753fafe90619cae296eb477(September 12, 2025), MIT License, https://github.com/Azure/azure-rest-api-specs/tree/37886acc4980d7ac8753fafe90619cae296eb477/specification/msi/resource-manager/Microsoft.ManagedIdentity/ManagedIdentity/stable/2024-11-30/examples (accessed Oct 30, 2026); pinned copy inexamples/third_party/msi-fic-rest-examples.↩︎Microsoft Learn, “Workload identity federation concepts,” how it works (the case-sensitive match), https://learn.microsoft.com/entra/workload-id/workload-identity-federation (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Deploy to Azure App Service by using GitHub Actions,” manually set up a GitHub Actions workflow (OpenID Connect), https://learn.microsoft.com/azure/app-service/deploy-github-actions (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Flexible federated identity credentials (preview),” properties; issuer URLs, supported claims and operators by platform, https://learn.microsoft.com/entra/workload-id/workload-identities-flexible-federated-identity-credentials, and “Set up a Flexible Federated identity credential (preview),” https://learn.microsoft.com/entra/workload-id/workload-identities-set-up-flexible-federated-identity-credential (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Important considerations and restrictions for federated identity credentials,” propagation; concurrent updates; errors, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Workload identity federation concepts,” how it works (the case-sensitive match), https://learn.microsoft.com/entra/workload-id/workload-identity-federation (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Create federatedIdentityCredential,” permissions, request body, national cloud deployments, https://learn.microsoft.com/graph/api/application-post-federatedidentitycredentials?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Configure a user-assigned managed identity to trust an external identity provider,” introduction, prerequisites, ARM template and REST samples, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Flexible federated identity credentials (preview),” properties; issuer URLs, supported claims and operators by platform, https://learn.microsoft.com/entra/workload-id/workload-identities-flexible-federated-identity-credentials, and “Set up a Flexible Federated identity credential (preview),” https://learn.microsoft.com/entra/workload-id/workload-identities-set-up-flexible-federated-identity-credential (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Configure an app to trust an external identity provider,” Azure CLI, Azure PowerShell and Microsoft Graph samples, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Flexible federated identity credentials (preview),” properties; issuer URLs, supported claims and operators by platform, https://learn.microsoft.com/entra/workload-id/workload-identities-flexible-federated-identity-credentials, and “Set up a Flexible Federated identity credential (preview),” https://learn.microsoft.com/entra/workload-id/workload-identities-set-up-flexible-federated-identity-credential (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” Azure CLI, Azure PowerShell and Microsoft Graph samples, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Mutable subjects in federated identity credentials,” recommended hygiene, https://learn.microsoft.com/entra/workload-id/workload-identities-federated-credential-mutable-subjects (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Microsoft identity platform and the OAuth 2.0 client credentials flow,” third case: access token request with a federated credential, https://learn.microsoft.com/entra/identity-platform/v2-oauth2-client-creds-grant-flow (accessed Oct 30, 2026).↩︎
Microsoft Learn, “List signIns” (beta), permissions, national cloud deployments, the
signInEventTypesnote, https://learn.microsoft.com/graph/api/signin-list?view=graph-rest-beta (accessed Oct 30, 2026).↩︎Microsoft Learn, “signIn resource type” (beta), https://learn.microsoft.com/graph/api/resources/signin?view=graph-rest-beta (accessed Oct 31, 2026).↩︎
Microsoft Learn, “Important considerations and restrictions for federated identity credentials,” propagation; concurrent updates; errors, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Important considerations and restrictions for federated identity credentials,” propagation; concurrent updates; errors, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations (accessed Oct 30, 2026).↩︎
Microsoft Learn, “List federatedIdentityCredentials,” permissions, https://learn.microsoft.com/graph/api/federatedidentitycredential-list?view=graph-rest-1.0 (accessed Oct 30, 2026; read for Chapter 01).↩︎
Microsoft Learn, “List signIns” (beta), permissions, national cloud deployments, the
signInEventTypesnote, https://learn.microsoft.com/graph/api/signin-list?view=graph-rest-beta (accessed Oct 30, 2026).↩︎Microsoft Learn, “List Azure role assignments using the REST API,” https://learn.microsoft.com/azure/role-based-access-control/role-assignments-list-rest, and “List Azure role assignments using the Azure portal,” prerequisites, https://learn.microsoft.com/azure/role-based-access-control/role-assignments-list-portal (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Create federatedIdentityCredential,” permissions, request body, national cloud deployments, https://learn.microsoft.com/graph/api/application-post-federatedidentitycredentials?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Azure built-in roles for Identity,” Managed Identity Contributor, https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/identity#managed-identity-contributor (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Create federatedIdentityCredential,” permissions, request body, national cloud deployments, https://learn.microsoft.com/graph/api/application-post-federatedidentitycredentials?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure a user-assigned managed identity to trust an external identity provider,” introduction, prerequisites, ARM template and REST samples, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Azure built-in roles for Identity,” Managed Identity Contributor, https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/identity#managed-identity-contributor (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Manage user-assigned managed identities using REST,” create and list, https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/manage-user-assigned-managed-identities-rest (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Important considerations and restrictions for federated identity credentials,” propagation; concurrent updates; errors, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Create federatedIdentityCredential,” permissions, request body, national cloud deployments, https://learn.microsoft.com/graph/api/application-post-federatedidentitycredentials?view=graph-rest-1.0 (accessed Oct 30, 2026).↩︎
Microsoft Learn, “List federatedIdentityCredentials,” permissions, https://learn.microsoft.com/graph/api/federatedidentitycredential-list?view=graph-rest-1.0 (accessed Oct 30, 2026; read for Chapter 01).↩︎
Microsoft Learn, “Configure an app to trust an external identity provider,” important considerations and restrictions (the include shared with the managed identity page), https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust (accessed Oct 30, 2026); pinned in
examples/third_party/entra-wif-create-trust-pages/includes.↩︎Microsoft Learn, “Workload identity federation concepts,” how it works (the case-sensitive match), https://learn.microsoft.com/entra/workload-id/workload-identity-federation (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure a user-assigned managed identity to trust an external identity provider,” introduction, prerequisites, ARM template and REST samples, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Federated Identity Credentials - Create Or Update” (Managed Identity REST, 2024-11-30), https://learn.microsoft.com/rest/api/managedidentity/federated-identity-credentials/create-or-update (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Configure a user-assigned managed identity to trust an external identity provider,” introduction, prerequisites, ARM template and REST samples, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-create-trust-user-assigned-managed-identity (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Important considerations and restrictions for federated identity credentials,” propagation; concurrent updates; errors, https://learn.microsoft.com/entra/workload-id/workload-identity-federation-considerations (accessed Oct 30, 2026).↩︎
Microsoft Learn, “User Assigned Identities - Create Or Update” (Managed Identity REST, 2024-11-30), https://learn.microsoft.com/rest/api/managedidentity/user-assigned-identities/create-or-update (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Manage user-assigned managed identities using REST,” create and list, https://learn.microsoft.com/entra/identity/managed-identities-azure-resources/manage-user-assigned-managed-identities-rest (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Azure built-in roles for Identity,” Managed Identity Contributor, https://learn.microsoft.com/azure/role-based-access-control/built-in-roles/identity#managed-identity-contributor (accessed Oct 30, 2026).↩︎
Microsoft Learn, “List Azure role assignments using the REST API,” https://learn.microsoft.com/azure/role-based-access-control/role-assignments-list-rest, and “List Azure role assignments using the Azure portal,” prerequisites, https://learn.microsoft.com/azure/role-based-access-control/role-assignments-list-portal (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Microsoft identity platform and the OAuth 2.0 client credentials flow,” third case: access token request with a federated credential, https://learn.microsoft.com/entra/identity-platform/v2-oauth2-client-creds-grant-flow (accessed Oct 30, 2026).↩︎
Microsoft Learn, “List signIns” (beta), permissions, national cloud deployments, the
signInEventTypesnote, https://learn.microsoft.com/graph/api/signin-list?view=graph-rest-beta (accessed Oct 30, 2026).↩︎Microsoft Learn, “signIn resource type” (beta), https://learn.microsoft.com/graph/api/resources/signin?view=graph-rest-beta (accessed Oct 31, 2026).↩︎
Microsoft Learn, “signIn resource type” (beta), https://learn.microsoft.com/graph/api/resources/signin?view=graph-rest-beta (accessed Oct 31, 2026).↩︎
Microsoft Learn, “Flexible federated identity credentials (preview),” properties; issuer URLs, supported claims and operators by platform, https://learn.microsoft.com/entra/workload-id/workload-identities-flexible-federated-identity-credentials, and “Set up a Flexible Federated identity credential (preview),” https://learn.microsoft.com/entra/workload-id/workload-identities-set-up-flexible-federated-identity-credential (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Flexible federated identity credentials (preview),” properties; issuer URLs, supported claims and operators by platform, https://learn.microsoft.com/entra/workload-id/workload-identities-flexible-federated-identity-credentials, and “Set up a Flexible Federated identity credential (preview),” https://learn.microsoft.com/entra/workload-id/workload-identities-set-up-flexible-federated-identity-credential (accessed Oct 30, 2026).↩︎
Microsoft Learn, “Mutable subjects in federated identity credentials,” recommended hygiene, https://learn.microsoft.com/entra/workload-id/workload-identities-federated-credential-mutable-subjects (accessed Oct 30, 2026).↩︎
National Institute of Standards and Technology, SP 800-53B Moderate baseline, release 5.2.0 OSCAL profile (includes AC-2, AC-2(3), AC-6, AU-2, AU-9, AU-12, CM-3, CM-3(2), CM-4(2), CM-5, IA-5, IA-5(2) and SI-7; IA-9 is not in it), https://github.com/usnistgov/oscal-content/blob/main/nist.gov/SP800-53/rev5/json/NIST_SP-800-53_rev5_MODERATE-baseline_profile.json (read November 2, 2026).↩︎
FedRAMP, “Key Security Indicators,” FedRAMP Consolidated Rules for 2026, indicator statements and “Related SP 800-53 Controls” (no indicator lists IA-9), https://www.fedramp.gov/2026/reference/key-security-indicators/ (read November 2, 2026).↩︎
Microsoft Learn, “Mutable subjects in federated identity credentials,” recommended hygiene, https://learn.microsoft.com/entra/workload-id/workload-identities-federated-credential-mutable-subjects (accessed Oct 30, 2026).↩︎









