OIDC: Trading a Static Key for a Question Amazon Answers Itself

How OIDC replaces a long-lived AWS key with a signed token that expires in an hour, why the setup cost is real, and where the credential actually goes in the workflow.

September 13, 20263 min read2 / 4

The original sin of this whole series was a static AWS key sitting in a repo secret, valid forever. OIDC is the fix that was promised back then.

The idea in one sentence: instead of GitHub handing AWS a password, GitHub and AWS agree to trust each other directly, and AWS mints a brand-new, short-lived credential on every single run.

What actually happens on each run

GitHub signs a token describing the run: which repo, which branch, which workflow. AWS checks that signature against a trust policy you set up once, and if the details match, AWS hands back a temporary credential, valid for as little as a few minutes or as long as an hour by default.

GitHub mints tokendescribes this exact run
→
AWS checks trust policyrepo, branch, environment must match
→
short-lived credentialexpires with the run

Nothing gets stored anywhere. There's no key to leak, because there's no key that outlives the job that requested it. A workflow file leaked from a completely different repo can't mint a credential for yours, because the trust policy only answers yes to the exact repo, branch, or environment it names.

Wiring it into the workflow

The change inside deploy.yaml itself is small:

YAML
permissions: id-token: write contents: read steps: - uses: aws-actions/configure-aws-credentials@v4 with: role-to-assume: arn:aws:iam::123456789012:role/fem-cicd-service aws-region: us-west-2

id-token: write is what lets the job request that signed token in the first place. role-to-assume points at an IAM role you created once, with a trust policy scoped to this specific repo. No aws-access-key-id, no aws-secret-access-key, nothing left to rotate or accidentally expose.

Scope that permissions block to the job that actually deploys, not the whole workflow. A build job that never touches AWS has no reason to be able to request a token at all, the same least-privilege instinct behind scoping an IAM policy to exactly what a role needs.

The real cost nobody skips past

Here's the part worth being honest about: every IAM role is bound to one specific repository. You cannot share a role across repos, not because GitHub won't let you, but because sharing it means every repo using that role shares the same attack surface. Change the role's permissions for one, you've changed it for all of them.

At a company with a handful of repos, that's a short setup task. At a company with a few thousand, that's a policy to write and maintain for every single one. This is the honest reason a lot of teams stall out on OIDC even though everyone agrees it's the right call: committing to it means committing to it everywhere, not just on the repos you personally care about.

There's a smaller, related limit worth knowing: tokens can't live forever, six hours is roughly the ceiling AWS allows. A pipeline that needs credentials for longer than that has to request a fresh token partway through, usually by moving the authentication step to just before the part of the job that actually needs AWS access, rather than at the very start.

The Essentials

  1. OIDC replaces a stored secret with a signed token AWS verifies on every run. Nothing is stored, nothing can be leaked from a repo secret, because there's no long-lived key to begin with.
  2. id-token: write plus role-to-assume is the entire workflow-side change. Scope it to the job that deploys, not the whole file.
  3. An IAM role can't be shared across repos. Every repo needs its own role and its own trust policy, which is real setup work at scale, not a one-time cost.

Further Reading and Watching