1. Home
  2. Articles
  3. Guide

Guide

Deploying to AWS from GitHub Actions without access keys

Long-lived AWS access keys stored in GitHub are one of the most common ways cloud accounts get compromised. OpenID Connect (OIDC) replaces them with short-lived credentials that GitHub requests for each run.

  • By Anwar Alawad
  • Updated 1 October 2026
  • Read 6 min
01 · Why

Why drop access keys

An access key stored as a GitHub secret works until someone copies it, a workflow leaks it in a log, or the person who created it leaves. It doesn’t expire unless someone rotates it, and it usually has more permissions than the pipeline needs.

With OIDC, each workflow run asks GitHub for a signed token that says which repository, branch or environment it is running from. AWS checks that token against rules you set, and hands back temporary credentials for one IAM role. Nothing long-lived is stored anywhere.

02 · Step 1

Step 1: add GitHub as an identity provider in AWS

In IAM, add an OpenID Connect identity provider with:

  • Provider URL: https://token.actions.githubusercontent.com
  • Audience: sts.amazonaws.com

You do this once per AWS account.

03 · Step 2

Step 2: create a role that only your repository can use

The role’s trust policy is where the security lives. It checks the token’s audience and its subject, which names the repository and the branch or environment:

trust-policy.json
{
  "Version": "2012-10-17",
  "Statement": [{
    "Effect": "Allow",
    "Principal": { "Federated": "arn:aws:iam::ACCOUNT_ID: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:your-org/your-repo:ref:refs/heads/main"
      }
    }
  }]
}

To tie the role to a GitHub environment instead of a branch, use a subject such as repo:your-org/your-repo:environment:production.

Don’t leave the subject open.

Without a sub condition, or with a wildcard like repo:your-org/*, any repository or branch that matches can assume the role, including a pull request branch.

04 · Step 3

Step 3: give the role only what the pipeline does

Attach a policy for exactly what the job needs: for a static site, writing to one bucket and invalidating one CloudFront distribution. Use separate roles for staging and production, so a staging workflow can never touch production.

05 · Step 4

Step 4: request the token in your workflow

The workflow needs permission to request an OIDC token, and then uses the official action to assume the role:

.github/workflows/deploy.yml
permissions:
  id-token: write   # lets the job request an OIDC token
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4
      - uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::ACCOUNT_ID:role/github-actions
          aws-region: ap-southeast-2
      - run: aws s3 sync dist/ s3://your-bucket/

Once that works, delete the old access keys from GitHub and deactivate them in IAM.

06 · Example

A working example

This website deploys this way: staging and production workflows both request a token with id-token: write and assume a role with aws-actions/configure-aws-credentials. The workflows use no stored AWS keys. See how devopsplant.com.au is built and deployed.

07 · Sources

Sources

Next step

Still have access keys in your pipelines?

On a 30-minute call we’ll look at where they are and what replacing them would take.