aws-actions/configure-aws-credentials: the OIDC trust policy, leg by leg
aws-actions/configure-aws-credentials exchanges the job's GitHub OIDC token for temporary AWS credentials by calling sts:AssumeRoleWithWebIdentity on the role you name, then exports them as environment variables for every later step. It needs id-token: write on the job, an IAM OIDC provider for token.actions.githubusercontent.com in the account, and a trust policy whose sub condition matches the subject your workflow actually sends.
The AWS step is usually the first one in a deploy job and the one most likely to fail on first setup. In our experience most first-setup failures are in IAM rather than in the YAML: the provider is missing from the account, or the trust policy expects a subject the workflow does not send.
That second case got a new cause this year. Repositories created after 15 July 2026, and older ones that opt in or are renamed or transferred after that date, send an immutable sub that carries the numeric owner and repository IDs, so a trust policy copied from an older guide denies them. Inputs and messages are from v6.3.0, read 2026-11-12.

How the token exchange works
GitHub's OIDC docs describe the flow in four steps, and the action maps onto them directly. First, the job asks GitHub for a JWT. The runner only injects the request variables when the job has id-token: write; the action asks for audience sts.amazonaws.com unless you set audience (China regions use sts.amazonaws.com.cn).
Second, the action calls STS AssumeRoleWithWebIdentity with that token. STS looks for an IAM OIDC provider for token.actions.githubusercontent.com in the role's account. Third, the role's trust policy checks the token's claims, normally aud and sub. Fourth, STS returns an access key, secret and session token, and the action exports them as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY and AWS_SESSION_TOKEN, plus AWS_REGION. A post step removes them at the end of the job.
The action exports the session credentials, not the token, so nothing in the job can renew them. If a job runs longer than role-duration-seconds (default 3600), later AWS calls fail on expired credentials.
permissions:
id-token: write
contents: read
jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
with:
role-to-assume: arn:aws:iam::123456789012:role/deploy
aws-region: us-east-1
role-session-name: run-${{ github.run_id }}
- run: aws sts get-caller-identityThe trust policy and the sub condition
GitHub says you must define at least one condition so untrusted repositories cannot assume the role, and AWS recommends evaluating token.actions.githubusercontent.com:sub. Without any condition, the README warns, any GitHub user or repository could potentially assume the role, and an aud check alone does not change that, because any workflow can request the sts.amazonaws.com audience.
The subject is built from the job, and the rules decide which form your workflow sends. A job that references an environment sends environment:<name> instead of the ref. A pull_request run sends pull_request unless it references an environment. Colons inside values become %3A. The README warns against ForAllValues: in Allow statements, because those operators return true when the claim is absent.
{
"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",
"token.actions.githubusercontent.com:sub": "repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main"
}
}
}]
}| The job runs | sub it sends |
|---|---|
| On a branch push | repo:octo-org/octo-repo:ref:refs/heads/main |
| On a tag | repo:octo-org/octo-repo:ref:refs/tags/v1.2.0 |
With environment: prod | repo:octo-org/octo-repo:environment:prod |
On pull_request, no environment | repo:octo-org/octo-repo:pull_request |
| Immutable claims, branch push | repo:octo-org@123456/octo-repo@456789:ref:refs/heads/main |
Access keys, the fallback
OIDC is the recommended path and the README calls it that. Keys still work: put an IAM user's key pair in secrets and pass aws-access-key-id and aws-secret-access-key. Alone they are re-exported as they are. With role-to-assume the action uses them to call AssumeRole, so the job holds session credentials rather than the user's key, and role-external-id is available when the role requires one.
The keys are long-lived, so they need rotating and they sit in every job that can read the secret. Use them when the role cannot trust GitHub's provider, not as a shortcut past setting one up.
- uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: us-east-2
role-to-assume: arn:aws:iam::123456789012:role/deploy
role-external-id: ${{ secrets.AWS_ROLE_EXTERNAL_ID }}
role-duration-seconds: 1200Role chaining and session duration
A second step with role-chaining: true uses the credentials already in the environment to assume another role, typically in another account. The second role's trust policy must allow sts:AssumeRole and sts:TagSession for the first role, or you set role-skip-session-tagging: true on the second step.
role-duration-seconds defaults to 3600 and accepts 900 to 43200, but STS fails the call if you ask for more than the role's maximum session duration, which is set on the role between 1 and 12 hours. Chaining has a harder cap: AWS limits a chained session to one hour regardless of the role's setting, and a DurationSeconds above that fails.
- uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
with:
role-to-assume: arn:aws:iam::123456789012:role/ci
aws-region: us-east-1
- uses: aws-actions/configure-aws-credentials@e1253824e5c10ff9df46874f81ed3ec929e19cfd # v6.3.0
with:
role-to-assume: arn:aws:iam::210987654321:role/deploy
aws-region: us-east-1
role-chaining: true
role-duration-seconds: 3600The common errors, leg by leg
Each message carries a prefix from the action and a reason from GitHub or AWS. The prefix tells you the leg. One thing slows debugging: the AssumeRole call is retried up to 12 times with exponential backoff by default, so a trust-policy denial repeats before it fails. Set disable-retry: true while you iterate on IAM.
If the token claims are the question, the README points to github/actions-oidc-debugger, but that repository is now archived. Its archival notice gives a plain run step instead, which prints the decoded claims and never the token itself; the OIDC trust page carries it. Set its AUDIENCE to sts.amazonaws.com, run it before this action in a private repository, then delete it. The leg-one failures have their own pages: failed to get ID token and id-token write not granted.
| Message | Leg | Usual cause |
|---|---|---|
getIDToken call failed: ... | 1, token request | Requesting the GitHub token failed; the wrapped reason says why |
It looks like you might be trying to authenticate with OIDC. Did you mean to set the id-token permission? then Credentials could not be loaded, please check your action inputs: ... | 1, never attempted | id-token: write missing, so the action fell back to ambient credentials |
Could not assume role with OIDC: No OpenIDConnect provider found in your account for https://token.actions.githubusercontent.com | 2, provider | No IAM OIDC provider in the role's account, or a typo in the ARN |
Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity | 3, trust policy | sub or aud mismatch: an environment, a pull request, or immutable claims |
Key takeaways
- Three things must exist:
id-token: write, an IAM OIDC provider in the account, and a trust policy whosesubmatches. - Repositories created after 15 July 2026 send
org@ID/repo@IDinsub; older trust policies deny them. - An environment replaces the ref in
sub, and a pull request sendspull_request. - Nothing renews the exported credentials, and chained sessions cap at one hour.
Frequently asked questions
Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity. How do I fix it?
sub your job sends with the trust policy's condition. The usual mismatches are a job with an environment: (which replaces the ref), a pull_request trigger, or a repository on immutable claims sending org@ID/repo@ID. Print the decoded claims from the job with the run step in the archived github/actions-oidc-debugger README and copy the value rather than guessing.Why do I get "No OpenIDConnect provider found in your account"?
https://token.actions.githubusercontent.com in the account that owns the role. Create it with aws iam create-open-id-connect-provider --url https://token.actions.githubusercontent.com --client-id-list sts.amazonaws.com, once per account; the README says a thumbprint is no longer needed.Could not load credentials from any providers in GitHub Actions: what does it mean?
role-to-assume set, the action's source calls a missing id-token: write the likely cause, though not a certain one, and the log line before it asks exactly that.How long do credentials from configure-aws-credentials last?
role-duration-seconds between 900 and 43200, within the role's maximum session duration; a chained role is capped at one hour. The action does not refresh them, so size the duration to the job.Related guides
References
- aws-actions/configure-aws-credentials README at v6.3.0 (read 2026-11-12)
- configure-aws-credentials src/index.ts at v6.3.0
- GitHub docs: configuring OpenID Connect in Amazon Web Services
- GitHub docs: OpenID Connect reference, subject claims and immutable claims
- AWS IAM: roles terms and concepts, role chaining
- The Cargo Book
- GitHub Actions documentation