GitHub Actions provide the ability to define a build workflow, which per your requirements may include calls to AWS infrastructure.
However, for those AWS calls to work you need to establish an IAM identity. In the past, you would have to rely on AWS access keys set in project variables/secrets, or applying an IAM role to the compute of a self-hosted GitHub Runner.
A better way is to use the GitHub OIDC integration, which generates a signed JWT token that can be validated by the AWS STS and exchanged for short-lived credentials when the workflow runs.
Overview
As a prerequisite, a trusted IAM Identity Provider is created on the AWS side for GitHub and retrieves the root CA certificate thumbprint that is used for verification of signed JWT coming from GitHub (OIDC provider specifically for GitHub Actions is located at https://token.actions.githubusercontent.com).
An IAM role that governs the permissions on a set of AWS resources is created and lists this Identity Provider as its principal. This role defines conditionals on the incoming JWT claim such as audience, repository, and branch.
Then when a GitHub workflow action is run, a JWT token signed by GitHub is injected into the job context . This is sent to the AWS STS (Security Token Service) for authentication. In OIDC terms, GitHub is the Identity Provider and AWS is the Relying Party.
If the JWT claims match the IAM role conditions AND is signed by GitHub, short-lived AWS credentials are provided back to the GitHub workflow action.
This set of short-lived credentials can then be used to invoke AWS service calls, limited by the policy permissions of the assumed IAM role.

AWS infrastructure
As a prerequisite, we need to create the AWS resources that support this OIDC federated trust:
- AWS Identity Provider
- IAM Role
- IAM Policy
This could be done manually or via AWS CLI calls, but we will use Terraform in this article. Here is the full terraform definitions for the snippets below.
Create Identity Provider
The aws_iam_openid_connect_provider is used to create the AWS Identity Provider.
data "tls_certificate" "github" {
url = "https://token.actions.githubusercontent.com/oauth/discovery/keys"
}
resource "aws_iam_openid_connect_provider" "github" {
url = "https://token.actions.githubusercontent.com"
client_id_list = ["https://sts.amazonaws.com"]
thumbprint_list = [data.tls_certificate.github.certificates.0.sha1_fingerprint]
}
The ‘client_id_list’ is the ‘Audience’ identifier. This is a unique identifier (not an actual URL target), and should be the URL of the entity evaluating the JWT, “https://sts.amazonaws.com”.
Create IAM Role
Create an IAM role that uses the newly created Identity Provider as a principal, and allows AssumeRoleWithWebIdentity when the audience and GitHub repository project path match.
resource "aws_iam_role" "github_ci_readwrite" {
name = "gh-oidc-role-s3-readwrite"
# IAM 'trust relationship' tab - who can assume this role
assume_role_policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Principal": {
"Federated": "${aws_iam_openid_connect_provider.github.arn}"
},
"Action": "sts:AssumeRoleWithWebIdentity",
"Condition": {
"StringEquals": {
"token.actions.githubusercontent.com:aud": "https://sts.amazonaws.com"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:fabianlee/github-pipeline-aws-oidc-auth-readwrite:*"
}
}
}
]
})
} # aws_iam_role
The ‘StringLike’ and wildcard “*” usage allows for any branch in this repository to assume the IAM role. The value “fabianlee/github-pipeline-aws-oidc-auth-readwrite” is clearly the path to my example repository and needs to be changed to match your environment.
Associate IAM policy with role
Assign the IAM role policy that defines which AWS resources and permissions are being granted to the IAM role. In this case, we grant full privileges to S3 storage.
resource "aws_iam_role_policy" "github_role_policy" {
name = "github_oidc_policy"
role = aws_iam_role.github_ci_readwrite.id
policy = jsonencode({
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:*"
],
"Resource": "*"
}
]
})
} # aws_iam_role_policy
Running example terraform
Use the Terraform scripts from my GitHub example project.
git clone https://github.com/fabianlee/github-pipeline-aws-oidc-auth-readwrite.git cd github-pipeline-aws-oidc-auth-readwrite/aws-infra # manually change terraform.tfvars to match your GitHub repository # establish aws credentials aws login # create AWS resources described above (Identity Provider, IAM) terraform init terraform plan # note the ouput IAM role ARN, which is needed by workflow terraform apply
GitHub Workflow
On the GitHub side, now we need to craft a workflow definition that get the GitHub JWT injected into an action, does the OIDC exchange with AWS, and uses the returned short-lived credentials to make general AWS API calls.
Inject JWT into Job context
The first thing we need is for a GitHub JWT to be inserted into the job context, this is done by setting the ‘id-token’ permissions to ‘write’.
permissions: id-token: write contents: read
OIDC exchange with AWS
Using the JWT Token and IAM role ARN, we want to exchange this for short-lived credentials. This can be done using the ‘aws-actions/configure-aws-credentials‘ action as shown below.
- name: Configure AWS credentials via OIDC
uses: aws-actions/configure-aws-credentials@v5
with:
role-to-assume: "${{ vars.AWS_ROLE_ARN }}"
role-session-name: GitHub_to_AWS_via_FederatedOIDC-${{ github.run_id }}
aws-region: "${{ vars.AWS_REGION }}"
audience: "${{ vars.OIDC_AUDIENCE }}"
retry-max-attempts: 2
Action that uses AWS authentication
With the configure-aws-credentials job as a previous action, we can now run an action that invokes AWS API calls to S3. The action below will list all buckets, create a bucket, then upload a file to that new bucket.
- name: Test access to S3
run: |
aws s3 ls
aws s3 mb s3://$S3_BUCKET_NAME --region ${{ vars.AWS_REGION }} || true # create s3 bucket
echo "run id: $GITHUB_RUN_ID date: $(date -u +'%Y-%m-%dT%H:%M:%SZ')" > test.txt
aws s3 cp test.txt s3://$S3_BUCKET_NAME/test.txt
echo "just UPLOADED test.txt to s3 bucket $S3_BUCKET_NAME"
Here is the full aws.yaml workflow definition.
Create workflow variables
There are several variables used in the workflow definition above that need to be set at the GitHub repository level. Setting them as variables (instead of hardcoding into the workflow) allows more flexibility.
In GitHub, navigate to Settings > Secrets and Variables > Actions, click on the ‘Variables’ tab, then press “New repository variable”.![]()
Create the following variables:
- AWS_REGION = us-east-1 or any other region where you want S3 bucket created
- AWS_ROLE_ARN = arn:aws:iam::xxxxxxx:role/gh-oidc-role-s3-readwrite (role ARN to be assumed)
- OIDC_AUDIENCE = https://sts.amazonaws.com

Invoke workflow
You can invoke the example GitHub workflow either by pushing to the repository, or navigating to Actions, selecting”test GitHub workflow authentication into AWS using OIDC”, and pressing “Run workflow”.
The invoked jobs will exercise the JWT token injection, OIDC exchange, then use the short-lived credentials to make calls to S3 as a test. Clicking into the workflow run will show the details of each action.

Navigating to Amazon S3 >Buckets will show the new bucket and “test.txt” content just uploaded by the GitHub job.

REFERENCES
GitHub docs, Overview of OpenID Connect (OIDC)
GitHub docs, OpenID Connect reference
GitHub docs, Configuring OpenID Connect in Amazon Web Services
Amazon blog, Use IAM roles to connect GitHub Actions o actions in AWS
Firefly, Integration OIDC with Github Action to Manage Terraform Deployment on AWS
Ravinda Singh, Securely Connect GitHub Actions to AWS using IAM roles and OIDC
Igor Zhivilo, OpenID Connect and Github Actions to authentication with AWS
github.com, source for action, configure-aws-credentials
Tobias Schmidt, Connect GitHub Actions to AWS – OIDC Auth Guide, specific bucket names
GitHub docs, configuring customized claims
GitHub REST api, PUT customized claim template
NOTES
github discovery endpoint for Actions
https://token.actions.githubusercontent.com/.well-known/jwks