Required claims for GitHub flexible federated identity credentials
Scenario
- You have a large GitHub organisation with many repositories, all needing the same Azure access in some way, shape or form.
- Traditional federated identity credentials match the
subjectclaim as an exact string, and Entra allows only 20 per application. - Flexible federated identity credentials replace that exact string with an expression, so one credential can cover them all
claims['sub'] matches 'repo:contoso/contoso-repo:ref:refs/heads/*'
The example above is straight from the Microsoft documentation. It’s also rejected (fun) when tried, so I had to do some light digging.
What the docs claim
The concept page states, for the GitHub issuer:
Claim
subsupports operatorseqandmatchesClaimjob_workflow_refsupports operatorseqandmatches
and gives three examples, all built from those two claims. Creation goes through Microsoft Graph beta or the portal only. Azure CLI, Azure PowerShell and the Terraform providers all error on flexible credentials.
Every one of those examples returns the same error (fun, fun, fun):
The FederatedIdentityCredential.ClaimsMatchingExpression.Value is invalid.
Rule exception: Expression configured for issuer 'https://token.actions.githubusercontent.com'
either lacks all required claims or contains unallowed claims.
Down the rabbit hole
The message names neither the missing claim nor the rule, so that led to me (and my friend Claude) probing 92 combinations across eight dimensions (side note: I thought there were only three dimensions, but I guess Claude knows better…).
This was done one variable at a time, with a plain-subject control in every run. That control is what let us rule out other issues such as permissions, the app object, the beta endpoint and pure skill issues on our behalf.
We made some headway by using a deliberately invalid test. claims['actor'] eq 'x' returns a different message, naming the claim, which meant the claim table was observable by brute force. Enumerating 24 GitHub OIDC claims against both operators turned up exactly six permitted pairs, two of which appear nowhere on the flexible-credentials page:
repository_idrepository_owner_id- Both with
eqonly.
And then, of course, hindsight is 20/20 once you know what to look for. Microsoft does document this, in precisely one place (that I could find), a how-to guide about something else entirely, Migrate GitHub Actions federated credentials to immutable subjects.
For GitHub, a flexible federated identity credential must match the
subclaim and one or both of the following additional claims:repository_id…repository_owner_id… These additional claims are required regardless of whethersubuses a name-based, customized, or immutable format.
The feature’s own page neither says this nor links to it. Neither does the other how-to guide, Set up a Flexible Federated identity credential, whose GitHub example is rejected for exactly the same reason:
claims['sub'] matches 'repo:contoso/contoso-repo:ref:refs/heads/*' and claims['job_workflow_ref'] matches 'contoso/contoso-prod/.github/workflows/*.yml@refs/heads/main'
So of the three Learn pages covering this feature that I found, one documents the rule and two publish examples that plainly don’t work.
What works
The expression must contain sub, and at least one of repository_id or repository_owner_id compared with eq. Neither half alone is enough, and job_workflow_ref cannot substitute for either.
| Claim | eq | matches | On the feature’s docs page |
|---|---|---|---|
sub | yes | yes | yes |
job_workflow_ref | yes | yes | yes |
repository_id | yes | no | no |
repository_owner_id | yes | no | no |
Everything else is rejected for both operators, including the name-based repository and repository_owner. The id-based ones being eq-only makes a lot of sense when you think about it.
sub is the authorisation predicate, and the immutable numeric id is an anti-spoofing anchor of sorts. A name wildcard like repo:contoso/contoso-* is claimable by anyone who can create a matching repository name. It also stops matching after an organisation rename, while repository_owner_id supposedly remains stable across renames and is not reused.
Practical example
Get the organisation id:
gh api orgs/contoso --jq .id
Then create the credential. Note the issuer has no trailing slash, and audiences must be a real JSON array:
$appObjectId below is the Microsoft Entra application object ID, not its application (client) ID.
$body = [ordered]@{
name = 'contoso-all-repos'
issuer = 'https://token.actions.githubusercontent.com'
audiences = [string[]]@('api://AzureADTokenExchange')
claimsMatchingExpression = [ordered]@{
# The wildcard right after the org name absorbs the optional '@<owner_id>' that GitHub's
# immutable subjects add, so this matches both the old and new subject formats.
value = "claims['repository_owner_id'] eq '13371337'" +
" and claims['sub'] matches 'repo:contoso*/contoso-*:ref:refs/heads/*'" +
" and claims['job_workflow_ref'] matches 'contoso*/*/.github/workflows/deploy.yml@refs/heads/*'"
languageVersion = 1
}
}
$f = Join-Path $env:TEMP 'fic.json'
$body | ConvertTo-Json -Depth 5 | Set-Content $f -Encoding utf8NoBOM
az rest --method POST `
--url "https://graph.microsoft.com/beta/applications/$appObjectId/federatedIdentityCredentials" `
--body "@$f"
Remove-Item $f
So now you can use one credential for every repository in the organisation covered by all three rules in the expression. There are three independent constraints in this example:
- The org pinned by an id that cannot be forged or renamed
- The repository name prefixed
- Only jobs using
deploy.ymlas a reusable workflow able to mint a token
That last clause is optional. job_workflow_ref identifies the reusable workflow used by the job, not the calling workflow.
For GitHub.com, since 15 July 2026 GitHub emits an immutable subject (repo:contoso@13371337/repo@456:ref:…) for repositories that are created, renamed or transferred. This does not apply to GitHub Enterprise Server.
Check the configured prefix for a repository with gh api repos/contoso/repo/actions/oidc/customization/sub --jq .sub_claim_prefix. In my testing, the adjacent use_immutable_subject flag read false on repositories whose prefix already carried the IDs.
Sources
- Migrate GitHub Actions federated credentials to immutable subjects - this page has the correct information.
- Flexible federated identity credentials (preview) - examples are rejected.
- Set up a Flexible Federated identity credential (preview) - examples are rejected.
- Federated identity credential limit (20 per application).
- Immutable subject claims for GitHub Actions OIDC tokens.
- GitHub OIDC token claims reference.
- GitHub Actions OIDC with Azure flexible federated identity credentials.