Introduction
OpenID Connect (OIDC) authentication between GitHub Actions and AWS provides a secure way to access AWS resources without storing long-lived credentials in GitHub. Instead of static AWS access keys, GitHub Actions generates short-lived OIDC tokens that AWS validates through an IAM Identity Provider. This approach eliminates credential rotation requirements and reduces the blast radius of compromised secrets.
When OIDC authentication fails, the error message typically shows AccessDenied during AssumeRoleWithWebIdentity. The most common cause is that the IAM trust policy conditions don't match the claims in the OIDC token that GitHub sends. The trust policy must precisely match the repository name, branch reference, workflow environment, or other claims contained in the token. Even minor mismatches—a missing colon, wrong branch name, incorrect audience—cause authentication to fail.
Understanding the OIDC token claims and how IAM trust policies validate them is essential for configuring successful cross-provider authentication. The sub (subject) claim contains the repository and workflow context, while the aud (audience) claim identifies the intended recipient of the token.
Symptoms
When GitHub Actions OIDC AWS authentication fails, you will observe these symptoms:
configure-aws-credentialsaction fails withAccessDeniederror- AWS rejects
AssumeRoleWithWebIdentityrequest with "not authorized to perform sts:AssumeRoleWithWebIdentity" - The OIDC provider exists in AWS IAM, but workflows still cannot assume the role
- The same workflow works from one branch or repository but fails from another
- After repository rename or branch change, previously working workflows fail
- CloudTrail logs show "AssumeRoleWithWebIdentity" failures with policy mismatch reasons
Common error messages from GitHub Actions:
Error: User: arn:aws:sts::123456789012:assumed-role/GitHubActionsRole is not authorized to perform: sts:AssumeRole on resource: arn:aws:iam::123456789012:role/MyDeploymentRole
Error: AccessDenied: Not authorized to perform sts:AssumeRoleWithWebIdentityAWS configure-credentials action output:
Error: Could not assume role with OIDC: AccessDenied
Error: RequestError: send request failed caused by: Post "https://sts.amazonaws.com": AWS Error: AccessDeniedCommon Causes
Several factors cause OIDC trust policy mismatches:
- 1.**Wrong
subcondition pattern**: The trust policy'ssubcondition doesn't match the exact format GitHub sends. Common mistakes include missing therepo:prefix, wrong branch name format, or not accounting for workflow environment differences. - 2.Incorrect audience value: The
audcondition doesn't match what the GitHub Actionsconfigure-aws-credentialsaction requests. The default audience issts.amazonaws.com. - 3.**Missing
id-token: writepermission**: The workflow doesn't grant theid-token: writepermission, preventing GitHub from generating the OIDC token at all. - 4.OIDC provider configuration issues: The IAM OIDC provider URL doesn't match GitHub's token endpoint, or the thumbprint configuration is outdated.
- 5.Trust policy too restrictive: The policy uses
StringEqualsinstead ofStringLike, making it unable to match patterns across branches or environments. - 6.Wrong repository name: After renaming a repository, the trust policy still references the old name.
- 7.Environment-specific mismatches: The trust policy doesn't account for different environments (production vs staging) or deployment workflows.
Step-by-Step Fix
Follow these steps to diagnose and resolve OIDC trust policy issues:
Step 1: Confirm the workflow grants OIDC token permission
Check that the workflow has the required permissions:
```yaml # In the workflow file, ensure id-token: write is granted permissions: id-token: write # Required for OIDC contents: read # Required to checkout repo
# Or at the job level jobs: deploy: permissions: id-token: write contents: read steps: - uses: aws-actions/configure-aws-credentials@v4 ```
Without id-token: write, GitHub won't generate the OIDC token needed for AWS authentication.
Step 2: Decode and inspect the actual OIDC token
Before fixing the trust policy, understand what claims GitHub sends:
```yaml # Add a step to decode the token for debugging jobs: debug-oidc: permissions: id-token: write steps: - name: Get OIDC Token id: oidc uses: actions/github-script@v6 with: script: | const token = await core.getIDToken('sts.amazonaws.com'); console.log('Token received'); return token; result-encoding: string
- name: Decode Token Claims
- run: |
- TOKEN="${{ steps.oidc.outputs.result }}"
- # Split token and decode payload (middle section)
- PAYLOAD=$(echo $TOKEN | cut -d. -f2)
- # Add padding if needed
- PADDING_LEN=$((4 - ${#PAYLOAD} % 4))
- if [ $PADDING_LEN -ne 4 ]; then
- PAYLOAD="${PAYLOAD}$(printf '=%.0s' $(seq 1 $PADDING_LEN))"
- fi
- echo $PAYLOAD | base64 -d 2>/dev/null | jq . || echo "Manual decode needed"
`
Expected token claims structure:
{
"sub": "repo:myorg/myrepo:ref:refs/heads/main",
"aud": "sts.amazonaws.com",
"iss": "https://token.actions.githubusercontent.com",
"repository": "myorg/myrepo",
"repository_owner": "myorg",
"ref": "refs/heads/main",
"workflow": "deploy.yml",
"job_workflow_ref": "myorg/myrepo/.github/workflows/deploy.yml@refs/heads/main",
"event_name": "push",
"actor": "myuser"
}Step 3: Check the IAM trust policy conditions
Review the existing trust policy:
```bash # Get the role's trust policy aws iam get-role --role-name GitHubActionsRole --query 'Role.AssumeRolePolicyDocument' --output json
# Or via AWS Console: # IAM > Roles > GitHubActionsRole > Trust relationships ```
Verify the trust policy matches the token claims:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:*"
}
}
}
]
}Step 4: Correct the trust policy conditions
Update the trust policy to match GitHub's token format:
# Update trust policy via AWS CLI
aws iam update-assume-role-policy \
--role-name GitHubActionsRole \
--policy-document '{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:myorg/myrepo:*"
}
}
}
]
}'Common sub condition patterns:
```json // Match any branch in the repo "sub": "repo:myorg/myrepo:*"
// Match specific branch "sub": "repo:myorg/myrepo:ref:refs/heads/main"
// Match specific environment "sub": "repo:myorg/myrepo:environment:production"
// Match multiple branches "sub": "repo:myorg/myrepo:ref:refs/heads/*"
// Match specific workflow "sub": "repo:myorg/myrepo:workflow:deploy.yml" ```
Step 5: Verify OIDC provider configuration
Check the OIDC provider exists and is correctly configured:
```bash # List OIDC providers aws iam list-open-id-connect-providers
# Get provider details aws iam get-open-id-connect-provider --open-id-connect-provider-arn arn:aws:iam::123456789012:oidc-provider/token.actions.githubusercontent.com
# Verify the URL is correct (must be exactly) # https://token.actions.githubusercontent.com ```
Create OIDC provider if missing:
# Create GitHub OIDC provider
aws iam create-open-id-connect-provider \
--url https://token.actions.githubusercontent.com \
--client-id-list sts.amazonaws.com \
--thumbprint-list d69e56e89d0e5c5b7d2ea9cc9a8c0b02b7bf9d5f 69274b6ee75d54b834f56e5ec9a8c0b02b7bf9d5fStep 6: Test from the exact workflow context
OIDC conditions are context-sensitive. Test from the exact conditions the trust policy allows:
```yaml # Deploy workflow to test OIDC jobs: deploy: runs-on: ubuntu-latest permissions: id-token: write contents: read steps: - name: Configure AWS credentials uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/GitHubActionsRole aws-region: us-east-1
- name: Test AWS access
- run: |
- aws sts get-caller-identity
- aws s3 ls
`
Step 7: Check CloudTrail for specific denial reason
Review CloudTrail for detailed error information:
# Query CloudTrail for AssumeRole failures
aws cloudtrail lookup-events \
--lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \
--start-time $(date -u -d '1 hour ago' +%Y-%m-%dT%H:%M:%SZ) \
--end-time $(date -u +%Y-%m-%dT%H:%M:%SZ) \
--query 'Events[?contains(EventName, `AssumeRole`)]'Verification
After fixing the trust policy, verify OIDC authentication works:
```bash # Trigger workflow from allowed branch gh workflow run deploy.yml --ref main
# Watch workflow execution gh run watch
# Check AWS caller identity in workflow logs # Should show the assumed role ARN
# Verify in CloudTrail aws cloudtrail lookup-events \ --lookup-attributes AttributeKey=EventName,AttributeValue=AssumeRoleWithWebIdentity \ --query 'Events[0].CloudTrailEvent' --output text | jq '.userIdentity' ```
Expected successful output:
{
"type": "AssumedRole",
"principalId": "AROAEXAMPLE:GitHubActions",
"arn": "arn:aws:sts::123456789012:assumed-role/GitHubActionsRole/GitHubActions",
"accountId": "123456789012"
}Prevention
To prevent OIDC trust policy issues:
- 1.Treat trust policy as workflow configuration: Document the trust policy alongside the workflow. Changes to repository name, branch structure, or environment should trigger trust policy updates.
- 2.**Use
StringLikefor flexible matching**: Unless you need strict enforcement, useStringLikewith wildcards to allow multiple branches or environments.
```json // Flexible - allows any ref in repo "StringLike": { "sub": "repo:myorg/myrepo:*" }
// Strict - only main branch "StringEquals": { "sub": "repo:myorg/myrepo:ref:refs/heads/main" } ```
- 1.Test OIDC after repository changes: When renaming repositories, moving workflows, or changing branch names, immediately test OIDC authentication.
- 2.Use environment-specific roles: Create separate roles for different environments (production, staging) with specific
subconditions for each.
```json // Production role - only from main branch and production environment "StringEquals": { "sub": "repo:myorg/myrepo:environment:production" }
// Staging role - from develop branch "StringLike": { "sub": "repo:myorg/myrepo:ref:refs/heads/develop" } ```
- 1.Add OIDC verification to CI: Include a step that verifies OIDC authentication works before proceeding with deployment.
- name: Verify OIDC Authentication
run: |
CALLER=$(aws sts get-caller-identity --query Arn --output text)
if [[ ! "$CALLER" =~ "assumed-role/GitHubActionsRole" ]]; then
echo "OIDC authentication failed"
exit 1
fi
echo "OIDC authenticated successfully as: $CALLER"- 1.Document subject claim formats: Keep documentation showing the exact
subclaim format for different workflow triggers (push, pull_request, workflow_dispatch, environment).
Related Articles
- [WordPress troubleshooting: Fix IAM Access Denied 403 - Complete Tro](fix-iam-access-denied-403)
- [GitHub Actions Artifact Expired or 403 Download Failed](github-actions-artifact-expired-403-download-failed)
- [Fix Github Actions Artifact Upload Failed File Too Large 5gb Limit Issue in GitHub Actions](github-actions-artifact-upload-failed-file-too-large-5gb-limit)
- [Fix Github Actions Aws S3 Deploy Credentials Expired Rotate Issue in GitHub Actions](github-actions-aws-s3-deploy-credentials-expired-rotate)
- [Fix Github Actions Cache Evicted Manually Workflow Fails Download Issue in GitHub Actions](github-actions-cache-evicted-manually-workflow-fails-download)
<script type="application/ld+json"> { "@context": "https://schema.org", "@type": "TechArticle", "headline": "GitHub Actions OIDC AWS AssumeRole Failed Because the Trust Policy Did Not Match", "description": "Resolve GitHub Actions OIDC AssumeRoleWithWebIdentity failures by correcting IAM trust policy conditions, provider configuration, and workflow permissions.", "url": "https://www.fixwikihub.com/github-actions-oidc-aws-assume-role-trust-policy-failed", "publisher": { "@type": "Organization", "name": "FixWikiHub", "url": "https://www.fixwikihub.com" }, "author": { "@type": "Person", "name": "FixWikiHub Editorial Team" }, "datePublished": "2026-01-23T08:15:42.152Z", "dateModified": "2026-01-23T08:15:42.152Z" } </script>