A deployment workflow needs permission to update a cloud service. The convenient solution is to create an access key, save it as a GitHub secret, and let every deployment reuse it. That key may remain valid for months, and a leaked copy can outlive the workflow run that exposed it.
OpenID Connect changes the trust model. GitHub Actions requests a signed identity token for the current job. The cloud provider verifies its claims and exchanges it for short-lived credentials limited by a role. No permanent cloud key needs to live in the repository settings.
understand the three-party exchange
GitHub is the identity provider, your cloud platform is the relying party, and the workflow job is the subject requesting access.
workflow job
-> requests GitHub OIDC token
-> presents token to cloud identity service
-> cloud checks issuer, audience, repo, branch, and environment claims
-> cloud returns short-lived role credentials
-> deployment uses those credentials until they expire
OIDC does not make the workflow trusted automatically. The cloud trust policy decides which token claims are acceptable, and the role policy decides what an accepted job may do.
grant token permission only to the deployment job
GitHub requires the job to request id-token: write. That permission allows requesting an OIDC token; it does not directly grant write access to the repository or cloud.
name: deploy-production
on:
push:
branches: [main]
permissions:
contents: read
jobs:
deploy:
environment: production
permissions:
contents: read
id-token: write
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Configure cloud credentials
uses: cloud-vendor/configure-credentials@pinned-commit-sha
with:
role: production-deployer
- run: ./scripts/deploy.sh
Keep the permission on the narrow job that needs it, not at workflow scope when tests and lint jobs do not deploy. Pin third-party actions to reviewed commit SHAs for sensitive workflows rather than trusting a movable tag.
make the trust policy specific
A weak cloud policy trusts every workflow from an entire organization. A safer policy matches the exact issuer and audience plus a subject representing the repository and protected environment.
Conceptually:
{
"issuer": "https://token.actions.githubusercontent.com",
"audience": "cloud-security-token-service",
"subject": "repo:Awowowow/cryptoex:environment:production"
}
The exact fields and syntax differ across AWS, Azure, Google Cloud, and other providers. Use the cloud provider’s current documentation. The important design choice is stable: restrict by repository, organization, branch or tag, and protected environment as tightly as the workflow permits.
Do not trust arbitrary pull-request contexts for production. Code from forks should never obtain the same deployment role merely because CI runs against it.
separate identity trust from role permissions
The OIDC trust policy answers which workflow may assume a role. The role permissions answer what it can do after assumption. A production deployment may need to update one service and read one artifact bucket; it does not need account-wide administrator access.
Create separate roles for preview, staging, and production. Restrict resources and actions, set short session durations, and use cloud audit logs to record the assumed role and workflow identity. Environment separation limits damage from a mistake in a lower-risk workflow.
protect the GitHub environment
An exact environment claim is useful only if the environment itself is protected. Configure allowed deployment branches, required reviewers where appropriate, and environment-specific variables. Keep production deployment in one obvious workflow instead of letting many reusable paths assume the same role.
Be careful with pull_request_target. It runs in the base repository’s trusted context and can expose privileges if it checks out or executes untrusted pull-request code. A label or comment from an untrusted actor must not become permission to deploy.
roll out without creating a deployment outage
Migrate one non-production role first:
- Inventory every long-lived cloud credential in GitHub secrets.
- Identify the workflow, environment, and cloud actions each credential supports.
- Create a least-privilege role and exact OIDC trust policy.
- Add token permission to one staging deployment job.
- Verify successful deployment and cloud audit identity.
- Test that an untrusted branch, different repository, and wrong environment are denied.
- Move production after the denial tests pass.
- Revoke and delete the old key instead of leaving it as an undocumented fallback.
The denial tests are important. A successful main-branch deployment proves the happy path, but it does not prove that the trust policy excludes everything else.
plan for failure and rollback
OIDC introduces dependencies on GitHub’s token service, the cloud identity endpoint, trust-policy configuration, and runner time. Keep runner clocks synchronized, set bounded retries for token exchange, and distinguish identity failures from deployment failures in logs.
Rollback should change the application deployment, not silently restore a permanent administrator key. If emergency credentials are required, keep them in a separately controlled break-glass process with approval, monitoring, rotation, and a documented expiry.
observe every assumption
Record repository, workflow, commit SHA, environment, role, cloud session ID, and deployment result. Alert on role assumptions from unexpected branches, subjects, or times. Review unused trust relationships and roles periodically.
GitHub’s OIDC documentation explains the token and cloud-specific setup. The secure use reference covers broader workflow risks.
The benefit of OIDC is not that secrets disappear completely. Applications still have runtime secrets. The concrete improvement is that CI deployment identity becomes short-lived, attributable to one workflow run, and limited by two inspectable policies.
Review that identity path whenever a repository is renamed, transferred, made public, or split into reusable workflows. Subject claims and trust patterns that were narrow under the old structure can stop matching or become broader than intended after organizational changes. Treat the trust policy as deployed security code: keep it in version control where possible, require review, and test both acceptance and denial after every structural change.
Discussion
What would you try, change, or challenge after reading this guide? Specific results and errors help the next reader.
Comments will load as you reach this section.