Skip to main content
RunBook Academy

Git, CI/CD & GitOpsXLIII · OIDC and Short-Lived CredentialsGitHub

GitHub Actions OIDC in practice — id-token: write permission; the JWT issuance

Advanced⏱ ~25 mingit

What you'll learn

  • Configure the id-token: write permission in a GitHub Actions workflow
  • Describe what the OIDC ID token contains: issuer, audience, subject, and job-specific claims
  • Fetch the token with the actions/github-script action or the OIDC token API
  • Pass the token to a cloud authentication step (aws-actions/configure-aws-credentials, actions/azure/login, google-github-actions/auth)

Prerequisites

Verified against Git 2.55.x teaching target; 2.40+ minimum · GitHub Actions continuous service; Aug 2026 documentation baseline · Argo CD v3.5.x teaching target; v3.0+ minimum · Flux v2.9.x · Sigstore Cosign v3.1.x · SLSA v1.2 · OCI Distribution Specification v1.1 · Git LFS v3.7.1 · Kubernetes (cross-course target) 1.36.x

Not yet marked complete on this device.

A GitHub Actions workflow that uses OIDC federation declares the id-token: write permission, requests an OIDC ID token from GitHub’s OIDC endpoint, and passes the token to a cloud authentication step that exchanges it for a short-lived cloud session. The token is a JWT signed by GitHub’s OIDC provider; the token carries claims about the workflow, repository, branch, and job; the cloud validates the claims against its trust policy and issues a session. There is no static key on the runner; there is no rotation cadence to maintain.

The permission

The id-token: write permission grants the workflow the ability to request an OIDC ID token from GitHub’s OIDC endpoint. Without the permission, the workflow cannot request a token; the cloud authentication step fails.

flowchart LR
    W["Workflow declares\npermissions: id-token: write"] --> R["GitHub OIDC provider\nissues JWT"]
    R --> T["JWT signed by\ntoken.actions.githubusercontent.com"]
    T --> C["Cloud validates\nclaims and signature"]
    C --> S["Cloud issues\nshort-lived STS session"]

The permission is declared at the workflow level (applies to all jobs) or at the job level (applies to one job). The narrower the scope, the smaller the surface — a job that does not need OIDC should not have the permission.

permissions:
  id-token: write
  contents: read

The default for GITHUB_TOKEN permissions is read-only; the id-token: write permission is an explicit grant. Workflows that do not declare the permission cannot request a token.

What the token contains

The OIDC ID token is a JWT signed by GitHub’s OIDC provider. The token carries standard OIDC claims and GitHub-specific claims:

  • isshttps://token.actions.githubusercontent.com
  • aud — the audience the workflow requested (typically sts.amazonaws.com for AWS)
  • sub — the subject, a structured string identifying the workflow run (for example, repo:acme/infra:ref:refs/heads/main)
  • exp — the expiration timestamp (typically 5-10 minutes from issuance)
  • iat — the issuance timestamp
  • job_workflow_ref — the workflow file path (for example, .github/workflows/deploy.yml@refs/heads/main)
  • repository — the full repository name (acme/infra)
  • repository_owner — the organisation or user (acme)
  • actor — the user or app that triggered the workflow
  • ref — the git ref that triggered the workflow

The sub claim is the primary gate. The cloud’s trust policy matches the sub claim against allowed patterns; if the pattern does not match, the cloud denies. The job_workflow_ref claim is a finer-grained gate: the trust policy can match the workflow file path to ensure only a specific workflow can assume the role.

Fetching the token

The workflow fetches the token from GitHub’s OIDC endpoint using two environment variables GitHub injects into every job:

  • ACTIONS_ID_TOKEN_REQUEST_TOKEN — a bearer token the workflow uses to authenticate to the OIDC endpoint
  • ACTIONS_ID_TOKEN_REQUEST_URL — the URL of the OIDC endpoint
OID_TOKEN=$(curl -s -H "Authorization: Bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
  "${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=sts.amazonaws.com")

The audience query parameter requests a token intended for the specified cloud service. The cloud’s trust policy validates the audience; if the audience does not match the policy, the cloud denies.

In practice, the cloud authentication action (for example, aws-actions/configure-aws-credentials@v4) fetches the token internally; the workflow does not need to call the OIDC endpoint directly.

A complete workflow

A workflow that uses OIDC to deploy to AWS:

name: Deploy
on:
  push:
    branches: [main]

permissions:
  id-token: write
  contents: read

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Configure AWS credentials via OIDC
        uses: aws-actions/configure-aws-credentials@v4
        with:
          role-to-assume: arn:aws:iam::"$AWS_ACCOUNT_ID":role/github-actions-deploy
          aws-region: us-east-1

      - name: Deploy
        run: aws s3 sync ./build s3://"$BUCKET_NAME"

The workflow declares id-token: write; the aws-actions/configure-aws-credentials@v4 action fetches the OIDC token, exchanges it for an STS session, and configures the AWS CLI to use the session credentials. The job ends; the STS session expires; the credential is dead.

Common pitfalls

  • Permission not declared. The workflow does not declare id-token: write; the cloud authentication step fails with a permission error. The fix: declare the permission.
  • Audience mismatch. The workflow requests an audience the trust policy does not expect; the cloud denies. The fix: align the audience in the workflow with the audience in the trust policy.
  • Trust policy too broad. The trust policy allows any workflow in any branch to assume the role; OIDC is no better than a static key. The fix: narrow the trust policy to repo + branch + workflow path.
  • Trust policy too narrow. The trust policy allows only the default branch; a workflow on a feature branch cannot assume the role; the deploy fails. The fix: extend the trust policy to cover legitimate deploy branches, or move the deploy to the default branch only.

Production discipline

  1. id-token: write is declared at the job level, not the workflow level. Jobs that do not need OIDC do not have the permission.
  2. The trust policy is as narrow as the legitimate workflows require. The sub claim matches repo + branch; the job_workflow_ref claim matches the workflow file path.
  3. Fork PRs are opted in deliberately, not by default. A workflow that needs OIDC on a fork PR must explicitly opt in via the repository settings; the opt-in is documented and reviewed.
  4. The audience is explicit. The workflow requests the audience; the trust policy validates the audience; the cloud service accepts only the audience it was configured for.

Cross-course references

  • Git, CI/CD & GitOps — Part XLIII-02 (OIDC federation basics) covers the trust relationship between forge and cloud.
  • Git, CI/CD & GitOps — Part XLIII-04 (OIDC in AWS) covers the AWS-side configuration of the trust policy and the aws-actions/configure-aws-credentials action.
  • Git, CI/CD & GitOps — Part XXXVII-04 (Jobs and steps) covers the workflow job model that OIDC operates within.

Quiz

Knowledge check · 4 questions

  1. Q1. A GitHub Actions workflow needs to authenticate to AWS via OIDC. What is the minimum permission declaration required?

  2. Q2. A GitHub Actions workflow running on a fork pull request can request an OIDC ID token by default, without any repository configuration.

  3. Q3. Name the two environment variables GitHub injects into every job for OIDC token requests and explain what each one does.

  4. Q4. Diagnose why the OIDC authentication step fails and prescribe the fix.

    Team T configures OIDC federation between GitHub Actions and AWS. The workflow declares `permissions: contents: read`. The deploy step calls `aws-actions/configure-aws-credentials@v4` with `role-to-assume`. The step fails with: `Error: Could not assume role with OIDC: Not authorized to perform sts:AssumeRoleWithWebIdentity`. The IAM role's trust policy is correctly configured for repo, branch, and workflow path.

Passing score: 75%. Answers are checked in this browser.