# aws-actions/configure-aws-credentials: the OIDC trust policy, leg by leg

> Set up aws-actions/configure-aws-credentials v6 with OIDC: the trust policy, sub condition, role chaining, session length and what each error means.

Source: https://latchkey.dev/learn/actions/aws-actions-configure-aws-credentials  
Updated: 2026-11-12

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.

```.github/workflows/deploy.yml
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-identity
```

## The 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.

```IAM trust policy
{
  "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` |

> Subject formats and the 15 July 2026 immutable-claim cutover are from GitHub's OpenID Connect reference, read 2026-11-12. Immutable claims are not available on GitHub Enterprise Server.

## 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.

```.github/workflows/deploy.yml
- 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: 1200
```

## Role 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.

```.github/workflows/deploy.yml
- 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: 3600
```

## The 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](/learn/ci-security/github-actions-oidc-cloud-credentials) 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](/learn/github-actions/github-actions-oidc-id-token-error) and [id-token write not granted](/learn/github-actions/gha-oidc-id-token-permission).

| 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 |

> Prefixes are from src/index.ts, src/assumeRole.ts and src/CredentialsClient.ts at v6.3.0; the STS reasons are quoted from issues #1043 and #1137 and the README, read 2026-11-12. The README also notes that a role named "GitHubActions" has been reported not to work (issue #953).

## FAQ

### Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity. How do I fix it?

Compare the `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"?

STS found no IAM OIDC provider for `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?

On v6 it arrives after "Credentials could not be loaded, please check your action inputs", and it means the action fell back to the SDK's default chain and found nothing. With `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?

One hour by default. Set `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.

## References

- [aws-actions/configure-aws-credentials README at v6.3.0 (read 2026-11-12)](https://github.com/aws-actions/configure-aws-credentials/blob/v6.3.0/README.md)
- [configure-aws-credentials src/index.ts at v6.3.0](https://github.com/aws-actions/configure-aws-credentials/blob/v6.3.0/src/index.ts)
- [GitHub docs: configuring OpenID Connect in Amazon Web Services](https://docs.github.com/en/actions/how-tos/secure-your-work/security-harden-deployments/oidc-in-aws)
- [GitHub docs: OpenID Connect reference, subject claims and immutable claims](https://docs.github.com/en/actions/reference/security/oidc)
- [AWS IAM: roles terms and concepts, role chaining](https://docs.aws.amazon.com/IAM/latest/UserGuide/id_roles_terms-and-concepts.html)

---

Latchkey runs CI/CD that repairs its own failures. Agent entry points: https://latchkey.dev/agent.txt, https://latchkey.dev/openapi.json, https://latchkey.dev/llms.txt
