The final episode of the security phase discusses authenticating to the cloud without storing long-term credentials using OpenID Connect, from the JWT concept to configuring trust policies in AWS, GCP, and Azure.

In episode 9 we learned to store and use secrets safely. But there's one class of credentials that remains worrying even when stored as secrets: long-term credentials — AWS Access Keys, GCP Service Account JSON, and the like. Credentials like these are valid for months; once they leak, an attacker has a very long time to use them before you notice and rotate the key.
Episode 10 answers that problem with OpenID Connect (OIDC): authentication to the cloud without passwords, without long-term credentials. GitHub Actions acts as an identity provider issuing short-lived tokens, and the cloud provider simply trusts them. In this episode we'll unpack the concept, then put it into practice with AWS, GCP, and Azure.
Imagine a permanent key to an office building copied to many people. Every time one copy is lost, you must replace all the locks — inconvenient and expensive. That's the state of storing an AWS Access Key in secrets: one value, valid for a long time, spread across many repositories. If it leaks, rotation must be done manually, often too late, and can lock the team out of systems.
Besides that, long-term credentials require distribution — every repository that needs to deploy must be given a copy. The more copies, the wider the attack surface.
OIDC breaks that cycle by reversing the direction of the credential flow:
The result: the workflow never stores, reads, or sends long-term credentials. What exists is only a 15-minute token that is born and dies with the job. If the token leaks, its lifetime is already over before it could be used.
Note
For a workflow to be able to request a JWT, the job must declare the id-token: write permission — this is part of the least privilege principle from episode 9. Without that permission, the OIDC flow won't run, and that's intentional.
For AWS, the configuration has two steps. First, create an IAM Role and attach a trust policy that allows GitHub Actions to exchange tokens. The key is the sub condition that restricts which repositories and branches may use that role:
{
"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"
},
"StringLike": {
"token.actions.githubusercontent.com:sub": "repo:devvnull/app:ref:refs/heads/main"
}
}
}
]
}Second, in the workflow, use aws-actions/configure-aws-credentials@v4 with role-to-assume — and notice, there's no more aws-access-key-id:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- uses: aws-actions/configure-aws-credentials@v4
with:
role-to-assume: arn:aws:iam::123456789012:role/deploy-role
aws-region: ap-southeast-1
- run: aws s3 sync dist/ s3://devvnull-bucketAfter the configuration step, all aws commands in this job automatically use the temporary credentials from the token exchange — ready for aws s3 sync, aws deploy, or aws ecr push.
GCP uses the same mechanism, called Workload Identity Federation. You create a Workload Identity Pool along with an OIDC provider pointing at GitHub, then connect it to a service account. In the workflow, authentication is handled by google-github-actions/auth@v2:
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
id-token: write
contents: read
steps:
- uses: actions/checkout@v4
- id: auth
uses: google-github-actions/auth@v2
with:
workload_identity_provider: projects/123456789012/locations/global/workloadIdentityPools/ci-pool/providers/github-provider
service_account: ci-deploy@devvnull-prod.iam.gserviceaccount.com
- run: gcloud compute instances listWhat needs to be prepared once on the GCP side: the workload identity pool + provider (with attribute mapping that translates GitHub's sub claim), then grant roles/iam.workloadIdentityUser on the service account for the corresponding principal. After that, the workflow uses gcloud and gcloud auth as usual — without a leakable JSON key file.
Azure implements the federated credential pattern on a service principal. After the app registration is created and the federated credential points to your repository, login happens via azure/login@v2 with three identities — no client secret:
steps:
- uses: azure/login@v2
with:
client-id: 00000000-0000-0000-0000-000000000000
tenant-id: 00000000-0000-0000-0000-000000000000
subscription-id: 00000000-0000-0000-0000-000000000000After login, az commands (for example az deploy, az aks get-credentials) directly use those federated credentials.
Tip
OIDC's security key is the sub claim. Never let a trust policy accept all repositories — narrow it down to a repo:<owner>/<repo>:ref:refs/heads/main pattern for production roles, and give separate, more restricted roles for other branches or pull requests. Each repository and branch gets a role with the smallest possible permissions. This applies the least privilege from episode 9 at the cloud layer.
| Mistake | Symptom | Solution |
|---|---|---|
Forgetting id-token: write permission | Job can't obtain a JWT | Add permissions: id-token: write |
| Trust policy too loose | Other repos could use the role | Tighten the sub claim per repo and ref |
| Role without least-action | Excess rights on temporary credentials | Apply minimum permissions on the role policy |
| Keeping old Access Keys in parallel | Static credentials that could still leak | Delete the keys, move fully to OIDC |
| Wrong GCP attribute mapping | Token rejected by provider | Match the mapping to GitHub's sub claim |
With OIDC, your pipeline reaches a security level that was previously impossible just by hiding credentials:
configure-aws-credentials@v4 + role-to-assume, GCP via auth@v2 + workload identity, Azure via azure/login@v2.sub claim becomes the restricting key: each repo, branch, and environment gets a separate role with minimal permissions.Our security phase is complete. In the next episode 11, we enter the reuse phase: Reusable Workflows & Composite Actions — packaging pipeline logic so it isn't duplicated across an organization's many repositories, applying the DRY principle we've learned throughout this series. See you there!