· via dev.to (home feed)
Replacing static AWS keys in GitHub Actions with OIDC: a practical migration guide
A dev.to walkthrough explains how to swap long-lived AWS access keys in GitHub Actions secrets for short-lived STS credentials using OpenID Connect federation.

If your repositories still store AWS_ACCESS_KEY_ID and AWS_SECRET_ACCESS_KEY as GitHub Actions secrets, those credentials sit around indefinitely — until they leak. A walkthrough published on dev.to lays out the alternative: OpenID Connect (OIDC) federation, which lets each workflow run obtain short-lived AWS credentials with no static secret to steal, screenshot or forget to rotate.
How OIDC replaces the secret exchange
According to the dev.to post, GitHub's token service issues a signed JSON Web Token to a job, and AWS accepts it because an IAM identity provider in the account is configured to trust token.actions.githubusercontent.com, with sts.amazonaws.com registered as the audience. No secret changes hands. AWS STS then returns temporary credentials, which the configure-aws-credentials action makes available to the workflow. Sessions last one hour by default and can be extended up to the role's configured maximum session duration.
One identity provider per AWS account
The provider resource is shared by every workflow that assumes a role in the account, and the post flags a common mistake: recreating it per project. AWS rejects a duplicate provider with the same URL, and there is no reason to want one anyway. Recent releases of Terraform's AWS provider (v5.81.0 and newer) let you omit the thumbprint list because AWS now validates GitHub's provider against its trusted certificate-authority library. Providers created years ago with an explicit thumbprint keep working, so the advice is to leave them alone unless rebuilding from scratch.
The trust policy does the real work
The IAM role's trust policy is the main security control. The token's sub claim encodes the repository together with a branch, tag, environment or pull-request context, and the post recommends matching it exactly — for instance repo:my-org/my-repo:environment:production — rather than trusting anything from the organization. A wildcard such as repo:my-org/* lets any repository in the org assume the role, including newly created or loosely protected ones, or repos running a compromised third-party action. Pairing the condition with a GitHub environment that requires reviewers adds an approval step on top of the token match, and StringLike should only appear when a wildcard is deliberate.
Federation is not an excuse for broad permissions
A narrow trust policy does not justify a permissions policy of "Action": "*" on "Resource": "*". The post argues the assumed role should cover exactly what the pipeline does — pushing to one ECR repository, updating one ECS service, or writing to one S3 bucket — because OIDC setups often drift into loose permissions on the assumption that federation itself is the boundary. It also recommends separate roles for staging and production, and distinct roles for a pull-request workflow running terraform plan (mostly read access plus the state backend) and a merge-to-main workflow running terraform apply, which limits the damage a malicious pull request can do.
Workflow settings that commonly trip people up
The job or workflow needs permissions: id-token: write or the token request fails outright — a frequent setup error, the post notes. Declaring any permissions block sets unlisted scopes to none, so contents: read is needed for checkout. Referencing configure-aws-credentials as @main would hand a supply-chain compromise of that action direct access to cloud credentials; major-version tags are better but mutable, so pinning to a full commit SHA with a dependency bot to bump it is the strongest option. Setting role-session-name to something derived from github.run_id also makes every CloudTrail entry traceable to a specific workflow run; session names run 2 to 64 characters from a restricted character set, with no slashes allowed.
Two failure modes, two different causes
The post distinguishes the two errors teams usually hit. A missing ACTIONS_ID_TOKEN_REQUEST_URL environment variable means the token was never requested — the id-token permission is absent. A Not authorized to perform sts:AssumeRoleWithWebIdentity error means a token reached AWS and was rejected, typically a sub or aud mismatch in the trust policy or a wrong role ARN. Reading the message before changing anything saves debugging time.
Plan a gradual rollout
Real organisations rarely migrate every repository in one pull request, the post acknowledges, so expect a transition period where some repos still rely on static keys. Track which have moved over and audit for leftover secrets in GitHub settings rather than assuming the cleanup finished itself.
Why it matters
Long-lived access keys embedded in CI secrets are a standing target: they surface in logs, screenshots, cloned repositories and compromised third-party actions, and they stay valid until someone rotates them. Replacing them with per-run STS credentials removes that entire class of exposure using machinery AWS and GitHub already provide, while session naming turns incident questions like who deployed a change into a quick CloudTrail lookup. The dev.to post's central warning is worth keeping in mind: the hardening work in the trust and permissions policies, not the federation protocol itself, determines how safe the setup actually is.
- #aws
- #github-actions
- #oidc
- #cloud-security
- #ci-cd