Skip to content
Latchkey LogoLatchkey home

AADSTS70021 no matching federated identity record found, and its two siblings

AADSTS70021 no matching federated identity record found is Microsoft Entra ID saying it received a token from GitHub and could find no federated credential on the application whose issuer, subject and audience all matched it. The same sentence is carried by two other codes that do name which of the three was wrong, so the digits are the diagnosis and the words are shared.

Three Entra error codes sharing one sentence, with the detail each adds after it
The framed line is quoted from Azure/azure-dev#5473. The other two codes were recorded in Azure/azure-cli#25178 and JulianHayward/AzAPICall#54.

What this error means

A cloud login step fails after the OIDC token has been minted, which means the GitHub half worked. The message begins with a code and then a sentence about a presented assertion. Take the code apart before reading the sentence. The five-digit AADSTS70021 arrives with no detail about what failed to match. The six-digit AADSTS700212 continues ... presented assertion audience and then the audience it saw. The six-digit AADSTS700213 continues ... presented assertion subject and then the subject in quotes, usually with a follow-on sentence telling you to check the credential's subject, audience and issuer against it. The longer codes are far more useful, and a report quoting one of them as the other loses the only distinguishing information in the message.

Quoted from Azure/azure-dev#5473; Entra ID is a closed service and this is a third-party report of its output
AADSTS700213: No matching federated identity record found for presented assertion subject 'repo:JeffreyCA/azd-dev-prod:environment:dev'. Check your federated identity credential Subject, Audience and Issuer against the presented assertion.

One sentence, three codes

Entra ID is a closed service, so what follows is drawn from Microsoft's published error-code reference and from the text people have recorded in their own runs rather than from any source we can read. Across those reports the pattern is consistent: the words No matching federated identity record found for presented assertion are shared, and the code decides whether anything follows them.

That matters when you go looking for help. A search for the sentence returns results for all three codes mixed together, and advice written for a subject mismatch is useless for an audience one. Strip the sentence and search the code.

CodeWhat follows the shared sentenceWhat it tells you
AADSTS70021Nothing beyond a full stopSomething did not match; the message does not say what
AADSTS700212audience and the audience it receivedThe audience is the mismatch
AADSTS700213subject and the subject in quotesThe subject is the mismatch, and it is quoted for you

Common causes

The subject changed because the trigger or the environment changed

The largest cause, and usually the result of an edit that looked unrelated. Adding an environment: to a job, deploying from a tag as well as a branch, or moving a job into a reusable workflow all change the generated subject, while the credential still matches the old one. The code that names the subject quotes the new value straight back at you.

No credential exists for this repository at all

The plain case: the application was created, the workflow was written, and the federated credential step was skipped or applied to a different application. In our experience this is what the bare five-digit code most often turns out to be, since it offers no detail to narrow it with.

The issuer was stored with a trailing slash or a typo

A hand-copied URL that looks correct. Microsoft's own CLI documentation carried a sample with a trailing slash, which produced this error for anyone who followed it, and the correction was raised as a pull request against the docs rather than against any code.

The audience is wrong for this cloud

The global value is not universal. A sovereign tenant expects its own audience, and a workflow using the default sends something that tenant will never match. The code that names the audience prints the value it received, which distinguishes this from a subject problem immediately.

How to fix it

Read the code, not the sentence

  1. Take the digits after AADSTS and stop reading the shared sentence.
  2. On a code naming the subject, copy the quoted subject verbatim; that is the exact string a credential must carry.
  3. On a code naming the audience, compare the printed audience against what your tenant expects.
  4. On the bare five-digit code, you have no detail, so check all three fields of the credential in turn.

Register a credential for the subject the run actually presented

Create the federated credential with the subject taken out of the error message, the fixed GitHub issuer, and the audience your cloud uses. Pasting the subject rather than retyping it removes the whole class of near-miss mistakes.

Federated identity credential (illustrative)
{
  "name": "gha-environment-dev",
  "issuer": "https://token.actions.githubusercontent.com",
  "subject": "repo:my-org/my-repo:environment:dev",
  "audiences": ["api://AzureADTokenExchange"]
}

Add one credential per entity you deploy from

A credential matches a single subject, so a branch, a tag and an environment each need their own. Enumerating them once, when the deployment paths are designed, is cheaper than discovering each one at the moment a release fails.

Subjects to register (illustrative)
repo:my-org/my-repo:ref:refs/heads/main
repo:my-org/my-repo:ref:refs/tags/v1.0.0
repo:my-org/my-repo:environment:production
repo:my-org/my-repo:pull_request

Stop adding permissions to the workflow

  1. Confirm that the log contains an AADSTS code; if it does, the token was minted and presented.
  2. That rules out id-token: write and every other GitHub-side permission as the cause.
  3. Take the change to the Entra application registration instead.

The subject is generated, and it changes with the trigger

GitHub builds the subject claim from the run: a repository, then a qualifier describing how the workflow was reached. A push to a branch, a tag, a pull request and a run against a deployment environment all produce different subjects, and a job that calls a reusable workflow can add a further claim naming that workflow.

A federated credential matches one subject exactly. So a repository that deploys from main and from a tag needs two credentials, and one that adds an environment: to a job has changed the subject of every run of that job. That last case is the one that catches people, because adding an environment reads as a protection change rather than as an identity change, and the quoted failure above is exactly it: a subject scoped to an environment, against a credential that was not.

How the workflow was reachedShape of the subject claim
A push to a branchrepo:<owner>/<repo>:ref:refs/heads/<branch>
A tagrepo:<owner>/<repo>:ref:refs/tags/<tag>
A job naming an environmentrepo:<owner>/<repo>:environment:<name>
A pull requestrepo:<owner>/<repo>:pull_request

The issuer has to match character for character

The issuer is the least variable of the three and still accounts for a recurring class of this failure, because it is copied by hand and a URL can gain a trailing slash without looking any different. A pull request against Microsoft's own Azure CLI documentation was opened for precisely that: a sample that created a federated credential with a trailing slash on the issuer, which made the credential unusable from a GitHub Actions workflow and produced this error.

There is nothing clever to do about it. The value is fixed, it has no trailing slash, and the fix is to compare the stored credential against it rather than against what you remember typing.

the issuer, with no trailing slash
https://token.actions.githubusercontent.com

The audience is not the same everywhere

The audience for Entra ID in the global cloud is api://AzureADTokenExchange, and that is what the login actions send by default. A sovereign cloud does not use the same value: one public report records a script failing in Azure China against exactly this sentence, under the code that names the audience, because the tenant there expects api://AzureADTokenExchangeChina.

So an audience mismatch has two quite different origins. Either the workflow is sending a value the credential does not carry, or the tenant is one where the default value is not the right one. The code that names the audience prints what it received, which settles which of those you have.

What this failure proves about the GitHub side

It proves the GitHub half succeeded. A token was requested, minted and presented, which means the job had the id-token: write permission, the runner had the request variables, and the token endpoint answered. Everything before the exchange worked.

That is worth saying because the two failures are often triaged together. If the OIDC token never existed you would be reading a client-side message from @actions/core instead, with no AADSTS code in it at all: GitHub Actions OIDC failed to get ID token covers that side. Adding permissions does not move this error.

Why there is no recorded run on this page

Reproducing this would mean standing up an Entra application, registering a credential we knew to be wrong, and recording the rejection, which would teach a reader our tenant identifiers and nothing about theirs. It would also be a recording of a closed service that we could describe but not explain: we can see what Entra returned, never why. The three codes and their continuations are quoted from reports by people who hit them on their own tenants, and the claims about what Entra checks are scoped to Microsoft's own documentation.

How to prevent it

  • Treat a change to a job's environment, trigger or reusable-workflow structure as a change to the identity it presents.
  • Register credentials for every deployment path at the time the paths are created.
  • Paste the issuer from documentation and check for a trailing slash before saving.
  • Record which audience your tenant expects alongside the client and tenant identifiers, so a sovereign cloud is not a surprise.

Frequently asked questions

What is the difference between AADSTS70021, AADSTS700212 and AADSTS700213?
They share a sentence and differ in what they add to it. The five-digit code adds nothing, so it does not say which field failed to match. The two six-digit codes continue with audience and subject respectively and print the value that was presented, which makes them far easier to act on.
Does this error mean my workflow is missing id-token: write?
No. A code from Entra ID means a token was minted by GitHub and presented for exchange, so the permission was in place and the request succeeded. A missing permission fails earlier with a message from @actions/core and no AADSTS code in it at all.
Why did this start after I added an environment to the job?
Because the subject claim is generated from how the workflow was reached, and naming an environment changes it to the environment form. The credential still matches the branch form, so nothing matches any more. The code that names the subject quotes the new value for you to register.
Is the audience always api://AzureADTokenExchange?
Not in every cloud. That is the global value and the default the login actions send, but a sovereign tenant can expect a different one; one public report records Azure China expecting api://AzureADTokenExchangeChina. The code that names the audience prints what was received, which tells you which side to change.

Related guides

References

Registering the subject is a one-time fix. The release it unblocks runs on Latchkey at $0.0025/min at 2 vCPU. Start free → 30-day trial · No credit card