Plainwork

Plainwork / CI OIDC Atlas / AssumeRoleWithWebIdentity not authorized: OIDC fix

"Not authorized to perform sts:AssumeRoleWithWebIdentity" and AADSTS7002138: a 6-step checklist

Your CI job has a signed OIDC token, the cloud trust policy looks right, and the login still fails. On AWS the CI log usually ends with Not authorized to perform sts:AssumeRoleWithWebIdentity; on Azure it is AADSTS7002138: No matching federated identity record found for presented assertion subject. Both errors mean the same thing: one of three values in the token (issuer, audience, subject) did not match your policy character for character. This page is the order we would check them in, with the examples taken from public threads and vendor docs read on 13 Oct 2026.

1. Read the real token, not the one you expect

Policies are usually written from a docs example, so the first job is to see what the platform actually sent. Print the token in a throwaway step (never in a shared log, it is a short-lived credential) and paste it into the OIDC token debugger. It runs in your browser and does not send the token anywhere. You need three values: iss, aud and sub.

In an AWS re:Post question from 15 Aug 2026 a Forgejo user wrote that the IAM provider and trust policy "exactly match the token's issuer, audience, and subject" and it still failed. The only answer asked them to inspect the token's header and payload and check that sub matches the trust condition exactly. That is step 1.

2. Compare the subject, character by character

The subject is where almost every failure lives, because every platform builds it differently. GitHub's default is repo:ORG/REPO:ref:refs/heads/BRANCH, GitLab's is project_path:GROUP/PROJECT:ref_type:branch:ref:main, and Bitbucket joins UUIDs with colons. Things that differ by one character:

  • Environment instead of branch. If the job names an environment, GitHub's subject carries environment:NAME and not the ref, so a condition on ref:refs/heads/main never matches. This is the situation in a 2 Aug 2026 Q&A post: a tag-triggered job with environment: production and id-token: write that could not assume the role.
  • Letter case. Microsoft compares issuer, subject and audience case sensitively. A Microsoft Q&A thread about repo:…:environment:Production shows a third reporter in Nov 2025 who lowercased the environment and it "did not resolve it". The environment name in the credential must be written exactly as GitHub sends it.
  • The new GitHub format. GitHub's docs say repositories created after 15 July 2026 use a subject that includes the owner ID and repository ID (repo:ORG@ORG-ID/REPO@REPO-ID:…). A policy written as repo:octo-org/* matches none of them. See the old and immutable sub formats.
  • Special characters. A colon inside an environment name is written %3A in the subject.

3. Check how your policy compares, not just what it contains

  • AWS. StringEquals compares exactly. Only StringLike treats * and ? as wildcards. A pattern under StringEquals silently matches nothing.
  • Entra standard credentials. Wildcard characters are not supported in any federated credential property, so repo:octo-org/* cannot work there; you need one credential per subject or a flexible federated identity credential (preview).
  • Entra limits. Microsoft documents a 600-character cap on the subject and an upper limit of 20 federated credentials per app.

4. Check the audience your job requested

The aud in the token must equal what the cloud side expects. Each cloud has its own default (AWS actions normally request sts.amazonaws.com; Microsoft recommends api://AzureADTokenExchange), and platforms differ in whether a job can change it. Paste the token in the debugger and read aud against the audience configured on the IAM OIDC provider or federated credential.

5. Check the issuer, including self-hosted platforms

The issuer is a URL the cloud provider fetches the signing keys from, and it has to match your provider registration with no stray trailing slash. Self-hosted platforms (GitHub Enterprise Server, GitLab self-managed, Forgejo, Gitea, Octopus) use your own host, so a policy copied from the public SaaS issuer will not match. Octopus's docs example, for instance, shows an issuer with a trailing slash while the instance we read did not have one, which is why the dataset records the issuer from each platform's live discovery document.

6. Check the plumbing around the token

  • The job asked for a token at all: on GitHub that is id-token: write under permissions.
  • The token is fresh. OIDC job tokens are short lived, so a value pasted from an old run will fail whatever the policy says.
  • The role's trust policy names the right OIDC provider ARN and account, and the provider exists in that account.

Test a policy without another failed deploy

Paste the token and the sub from your trust policy into the debugger: it reports the first character where they differ, flags an old-format pattern against a new-format token and a case difference, and prints copy-ready AWS, Entra and GCP snippets. The free MIT token decoder does the same offline for four platforms. If you run more than one CI, the CI OIDC Atlas ($12) lists the issuer, audience, subject format, token lifetime and gotchas for 11 platforms, each cell with the vendor sentence behind it.

Sources read 13 Oct 2026: AWS re:Post, "AWS STS Rejects Valid Forgejo OIDC Token" (15 Aug 2026); Microsoft Q&A, "Github quit deploying to azure"; Microsoft Entra documentation on federated credentials and the August 2024 case-sensitivity change; GitHub Docs, OpenID Connect reference. Vendors change formats; re-read the current page before relying on a detail.

More from the catalogue

Other small tools

See everything →