When a GitHub Actions job fails to get cloud credentials through OpenID Connect, the error usually names the cloud, but the fault is just as often in the workflow file, the repository settings or the shape of the subject claim. This guide works through GitHub Actions OIDC failures from the GitHub side: the job cannot request a token at all, the cloud rejects the token, or the exchange succeeds and the next API call is denied. Each section gives the symptom, the likely causes from most to least common, where to look, the fix, and how to stop it coming back. Error strings are quoted from GitHub, AWS, Microsoft and Google documentation and the official login actions, checked on 11 November 2026. It is for platform, DevOps and cloud security engineers; generic federation faults on Kubernetes and between clouds are covered in the Multi-Cloud Workload Identity Troubleshooting Guide, which this page does not repeat.
Key takeaways
- Read the subject claim before touching the trust. The job's actual
subvalue, compared character for character with the cloud's trust condition, explains most rejections in all three clouds. - No
id-token: write, no token. Setting any permission in apermissions:block sets the rest tonone, so addingcontents: readalone silently removes OIDC. - Environments change the subject. A job that references an environment gets
repo:ORG/REPO:environment:NAME, not the branch form, so adding an environment to a working job breaks a branch-based trust. - The subject format is moving. Repositories created, renamed or transferred after 15 July 2026 emit an immutable subject that includes owner and repository IDs; trust conditions written for the name-only form stop matching.
- Never fix a mismatch with a wildcard. Widening the trust to a whole organisation or every ref lets any workflow in scope mint cloud credentials, which is exactly what attackers who inject workflows look for.
Where a GitHub Actions OIDC exchange fails
Every run follows the same sequence. The job asks the GitHub OIDC provider for a JSON Web Token (JWT) signed by https://token.actions.githubusercontent.com, a login action presents it to the cloud's token service, the cloud checks the issuer, audience and subject against a trust configuration, and returns short-lived credentials. The credentials are then used against cloud APIs, where ordinary authorisation applies. Locate the failing stage first.
| Stage | Typical symptom in the job log | Where the fault usually is |
|---|---|---|
| 1. Token request | Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable or the matching ..._TOKEN message | Workflow permissions:, fork or Dependabot trigger, reusable workflow caller |
| 2. Issuer and audience | Issuer "does not match the expected ones" (Google Cloud), invalid token, audience rejected | Enterprise issuer URL, audience input on the login action, provider registration in the cloud |
| 3. Subject and claim match | Not authorized to perform sts:AssumeRoleWithWebIdentity, AADSTS700213, Failed to generate Google Cloud federated token | Trust policy condition, federated credential subject, attribute condition |
| 4. Credential issue | Failed to generate OAuth 2.0 Access Token (Google Cloud), no subscription selected (Azure) | Impersonation binding, subscription or tenant input |
| 5. API authorisation | AccessDenied, AuthorizationFailed, PERMISSION_DENIED on a later step | Role grants, not OIDC. See the cloud-specific guides linked below |
Symptom 1: the job cannot get an OIDC token
The @actions/core toolkit throws Unable to get ACTIONS_ID_TOKEN_REQUEST_URL env variable or Unable to get ACTIONS_ID_TOKEN_REQUEST_TOKEN env variable when the runner was not given the request URL and bearer token. The login actions surface this as a failure to fetch the ID token. Other toolkit errors at this stage are Failed to get ID Token followed by an HTTP status, and Response json body do not have ID Token field.
Likely causes, most common first
- Missing permission: the job or workflow does not grant
id-token: write. GitHub's workflow syntax lists the values aswriteornoneand states that if you specify access for any permission, all unspecified ones are set tonone. A hardening change that addspermissions: contents: readat the top of the file is the classic trigger. - Fork pull requests: for
pull_requestevents from a fork, unless the repository setting to send write tokens to fork workflows is enabled, GitHub changes write permissions to read, soid-token: writeis not honoured. This is the intended behaviour, not a bug. - Dependabot runs: Dependabot-triggered runs are treated like fork runs, with a read-only token and no secrets.
- Reusable workflows: a called workflow cannot hold more permission than the caller grants, so the caller job must grant
id-token: writeas well.
Diagnose and fix
- Where to look: the "Set up job" section of the log lists the effective
GITHUB_TOKENpermissions. IfIdTokenis notwrite, the fix is in YAML. - Fix: grant
id-token: writeat job level on the deploy job only, alongside the minimum other permissions it needs. Do not grant it at workflow level for jobs that build or test untrusted code. - Fork contributions: do not enable write tokens for fork workflows to make OIDC work. Run cloud deployments on
pushto a protected branch or behind an environment with required reviewers instead. - Prevent: lint workflows for deploy jobs that use a cloud login action without
id-token: write, and forid-token: writegranted at workflow level.
Symptom 2: the cloud rejects the subject
This is the most common class. The token is valid, but its sub (or another claim used in a condition) does not equal what the trust expects. GitHub's OIDC reference defines these default subject formats:
| Job context | Default sub value | Notes |
|---|---|---|
| Job references an environment | repo:ORG-NAME/REPO-NAME:environment:ENVIRONMENT-NAME | Takes precedence over branch and pull request forms |
| Pull request event, no environment | repo:ORG-NAME/REPO-NAME:pull_request | No branch name in the subject |
| Branch, no environment | repo:ORG-NAME/REPO-NAME:ref:refs/heads/BRANCH-NAME | A renamed default branch changes it |
| Tag, no environment | repo:ORG-NAME/REPO-NAME:ref:refs/tags/TAG-NAME | Release workflows triggered by tags |
| Immutable format (repositories created, renamed or transferred after 15 July 2026) | e.g. repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main | Includes owner and repository IDs; not on GitHub Enterprise Server |
| Custom subject template | Whatever include_claim_keys defines | Replaces the whole default format |
Any : inside a metadata value is encoded as %3A. For reusable workflows, the job_workflow_ref claim carries the called workflow path, for example octo-org/octo-automation/.github/workflows/oidc.yml@refs/heads/main, and a custom template can include it in the subject.
Likely causes, most common first
- An environment was added to the job: the subject switched from
ref:toenvironment:. - Wrong event: the trust allows
ref:refs/heads/mainbut the run was a pull request, which emitspull_request. - Immutable subject: a new, renamed or transferred repository now emits the ID form. The
aws-actions/configure-aws-credentialsdocumentation namesNot authorized to perform sts:AssumeRoleWithWebIdentityas the result when a trust policy uses the legacy name-only form and the repository emits the immutable claim. - Custom template drift: an organisation or repository subject template was changed with
PUT /repos/{owner}/{repo}/actions/oidc/customization/sub(or the organisation equivalent) before the cloud trust was updated. GitHub advises creating the matching cloud condition first. - Case or typo: Entra ID matches subjects exactly;
AADSTS7002138says the subject matches case-insensitively but not case-sensitively.
Diagnose and fix
- Read the subject: the
Azure/loginaction prints the subject claim in its log; for other clouds, decode the token payload locally in a non-production run (never in an online decoder) and readsub,aud,iss,environment,ref,event_nameandjob_workflow_ref. - Fix the side that is wrong: if the workflow changed legitimately (new environment, immutable IDs), add an exact trust entry for the new subject. If the run should not have deployment rights (a pull request), the rejection is correct.
- Prefer environments for multiple branches: Microsoft notes that pattern matching is not supported for branches and tags in federated identity credentials and suggests an environment instead, which also lets you require reviewers.
- Prevent: manage trust conditions in infrastructure as code beside the workflow, and test subject changes in a non-production role first.
Cloud-specific error reference
| Cloud | Exact error text | Meaning and fix |
|---|---|---|
| AWS | Not authorized to perform sts:AssumeRoleWithWebIdentity | Role trust does not accept the token: check the token.actions.githubusercontent.com:sub and :aud conditions, the OIDC provider ARN in the principal, and the immutable subject form |
| AWS | Credentials loaded by the SDK do not match | The action's credential check failed after loading; the action documentation suggests the role-chaining input when chaining from existing credentials |
| AWS | Audience rejected | The action defaults audience to sts.amazonaws.com; the IAM OIDC provider must list the same client ID. Non-default partitions need the audience input |
| Microsoft Entra | AADSTS700213: No matching federated identity record found for presented assertion subject | No federated credential subject equals the token subject; copy the subject from the run log exactly |
| Microsoft Entra | AADSTS7002138 | Case differs between credential and token, or the credential includes numeric owner and repository IDs that the token does not contain |
| Google Cloud | Failed to generate Google Cloud federated token | The token was not admitted to the workload identity pool: check the attribute condition |
| Google Cloud | Failed to generate OAuth 2.0 Access Token | Service account impersonation failed: check the roles/iam.workloadIdentityUser binding and its principalSet |
| Google Cloud | The size of mapped attribute exceeds the 127 bytes limit. | A long repository or branch name in a mapped claim; shorten names or map a shorter claim |
| Google Cloud | The access token lifetime cannot exceed 3600 seconds. | Runner clock skew; fix NTP on self-hosted runners or set access_token_lifetime to about 3300s |
| Google Cloud | Issuer "does not match the expected ones" | Provider issuer differs from the token iss; GitHub Enterprise Cloud or Server may use a different issuer URL from the public one |
Two configuration details trip teams up repeatedly. On AWS, configure-aws-credentials now ignores any thumbprint you specify, so editing thumbprints does not fix a trust error. On Azure, Azure/login requires subscription-id unless allow-no-subscriptions is true, and its default audience is api://AzureADTokenExchange, which must match the federated credential. Step-by-step setup is in How to set up workload identity federation for GitHub Actions to AWS and How to Federate github actions to google cloud.
Symptom 3: login succeeds, the next step is denied
If the login action reports success and a later CLI or SDK call fails with AccessDenied, AuthorizationFailed or PERMISSION_DENIED, OIDC is working and the problem is authorisation on the role, service principal or service account. Do not edit the trust. Use the cloud's own tooling: IAM policy simulation on AWS, the activity log and role assignments on Azure (see the Azure AuthorizationFailed Troubleshooting Guide), and Policy Troubleshooter on Google Cloud (see the Google Cloud PERMISSION_DENIED Troubleshooting Guide). Allow for propagation after a new grant before you redesign anything.
A safe diagnostic procedure
- Classify the stage from the table above using the first error line, not the last.
- Check permissions in the "Set up job" log and the trigger event. Stop here if
IdTokenis notwrite. - Capture claims safely in a throwaway branch or non-production role: decode locally, record
iss,aud,sub,environment,job_workflow_ref, then let the token expire. - Diff the trust: compare those values with the AWS role trust policy, the Entra federated credential, or the Google Cloud attribute mapping and condition. Look for environment, event, case and ID-format differences.
- Change one thing, in code, with a pull request reviewed by the cloud owner. Add an exact entry rather than widening an existing one.
- Retest and roll back cleanly: re-run the job; if it still fails, revert the trust change rather than stacking a second change on top.
- Record the cause so the same subject change is caught by linting next time.
Decision guidance: fix, widen or redesign
| Situation | Preferred fix | Avoid |
|---|---|---|
| Several branches deploy to the same account | One environment with protection rules, trust on the environment subject | Wildcard on ref:* |
| Central deployment logic in a reusable workflow | Custom subject template including job_workflow_ref, trust pinned to that workflow path and ref | Trusting every repository in the organisation |
| Many repositories, one platform team | Per-repository roles or credentials generated from a template | One shared role with an organisation-wide subject |
| Repository renamed or transferred | Update to the immutable ID subject, which survives future renames | Keeping both old and new names indefinitely |
| Pull requests need cloud read access for plans | Separate read-only role trusted for pull_request | Reusing the deploy role |
Small organisations with a handful of repositories can manage exact subjects by hand in each cloud. Larger estates should generate trust entries from a repository inventory and enforce an organisation subject template, because per-cloud limits apply: Entra allows a maximum of 20 federated identity credentials per application or user-assigned managed identity, so a design that adds one credential per branch will hit the ceiling.
Where identity fits
Each trust entry is a non-human identity binding: a role, service principal or service account that any matching workflow can become. The subject condition is the only thing standing between a repository's code and the cloud permissions, so treat trust changes as privileged access changes with owner review. The GitHub Actions OIDC Federation Hardening Guide covers the design side, and the How to Audit entra workload identity federated credentials page covers review.
Detection and measurement
- AWS: CloudTrail
AssumeRoleWithWebIdentityevents record the role and session name; setrole-session-nameto include${{ github.run_id }}so each AWS action traces back to a run. Alert onUpdateAssumeRolePolicyfor CI roles. - Microsoft Entra: service principal sign-in logs show failed exchanges with their AADSTS codes; audit logs show federated credential changes.
- Google Cloud: Security Token Service and IAM audit logs show exchanges and
setIamPolicychanges toworkloadIdentityUserbindings. See the Google Cloud Audit Logs and Log Sinks Guide. - GitHub: organisation audit log entries for subject template changes and Actions permission settings.
Metrics that show the estate is healthy:
- OIDC exchange failure rate per repository, grouped by error code.
- Number of trust entries using wildcards or organisation-wide subjects (target zero, or documented exceptions with expiry).
- Repositories still holding long-lived cloud keys as secrets alongside an OIDC path.
- Median time to resolve an OIDC failure.
- Share of trust changes made through reviewed infrastructure as code.
Common failure patterns
- Widening to end an outage: changing
repo:org/app:ref:refs/heads/maintorepo:org/*restores deployments and lets every repository in the organisation assume the role. - Putting a key back "temporarily": a stored cloud key added to unblock a release becomes the long-lived secret OIDC was meant to remove. In the tj-actions/changed-files compromise, a poisoned action dumped CI secrets from runner memory into workflow logs.
- Granting
id-token: writeeverywhere: setting it at workflow level gives build and test jobs that run third-party code the ability to request tokens. In the Megalodon GitHub Actions attack, injected workflows harvested CI secrets and OIDC tokens; tight subject conditions and job-level permissions limit what a stolen token can do. - Enabling write tokens for forks: done to make pull request previews deploy, it hands cloud access to external contributors' code.
- Changing the subject template first: switching an organisation template before every cloud trust is updated breaks every repository at once.
- Debugging thumbprints: time spent on thumbprints that the AWS action now ignores, while the real fault is a subject mismatch.
Framework mapping
| Framework | Reference | Relevance |
|---|---|---|
| NIST SP 800-53 Rev. 5 | IA-9 Service Identification and Authentication | Pipelines authenticate to clouds with federated identity instead of shared secrets |
| NIST SP 800-53 Rev. 5 | IA-5 Authenticator Management | Removing long-lived keys that OIDC replaces |
| NIST SP 800-53 Rev. 5 | AC-6 Least Privilege; CM-3 Configuration Change Control | Exact subject conditions; reviewed trust changes |
| NIST CSF 2.0 | PR.AA-01, PR.AA-05 | Managing service identities and enforcing access policy |
| CIS Controls v8.1 | 6.8 Define and Maintain Role-Based Access Control | Per-repository roles rather than shared deploy roles |
| MITRE ATT&CK | T1528 Steal Application Access Token; T1550.001 Application Access Token; T1078.004 Cloud Accounts | What a compromised workflow does with tokens it can request |
Practitioner checklist
- Grant
id-token: writeonly on deploy jobs, at job level, and confirm it in the "Set up job" log. - Read the actual
subfrom a run before editing any trust condition. - Check whether the job uses an environment, a pull request trigger or a reusable workflow, as each changes the claims.
- Plan for the immutable owner and repository ID subject on new, renamed and transferred repositories.
- Update cloud trusts before changing an organisation or repository subject template.
- Keep trust entries exact; record any temporary widening as an exception with an expiry.
- Never re-add a stored cloud key to unblock a deployment.
- Put the run ID in session names and alert on trust policy and federated credential changes.
- Track exchange failures by error code and remove stale trust entries quarterly.
Standards and references
- GitHub Docs: OpenID Connect reference
- GitHub Docs: Workflow syntax for GitHub Actions (permissions)
- Microsoft Learn: Configure an app to trust an external identity provider
- AWS: Create an OpenID Connect (OIDC) identity provider in IAM
- AWS: configure-aws-credentials action
- Microsoft: Azure/login action
- Google: google-github-actions/auth troubleshooting
- GitHub: actions/toolkit OIDC utilities source
Related NHI Mgmt Group resources: GitHub Actions OIDC Federation Hardening Guide · Multi-Cloud Workload Identity Troubleshooting Guide · GitHub and GitHub Actions Security Guide · Workload Identity Federation Trust Review Checklist