google-github-actions/auth: Workload Identity Federation in two legs
google-github-actions/auth exchanges the job's GitHub OIDC token at Google's Security Token Service, through a Workload Identity Pool provider you configure, and writes a credentials file that gcloud and the Google client libraries pick up in later steps. Name a service_account and that federated identity impersonates it; leave it out and you grant IAM roles to the pool's principalSet directly, which the README calls the preferred setup.
The usual story is a green auth step and a red step three lines later, or a red auth step whose message lists an endpoint and a status but no IAM role. Both make sense once you know there are two legs, that the action always runs the first one, and that it runs the second only when you ask for a token.
Every GitHub organization's tokens come from one issuer, which is why Google says a GitHub provider must carry an attribute condition that restricts it to your organization. Everything here is from v3.0.0 of the action, its source and troubleshooting doc, and Google's federation guide, read 2026-11-12.

How the exchange works
The action requests a GitHub OIDC token whose audience defaults to https://iam.googleapis.com/ followed by your provider's full name, and posts it to https://sts.googleapis.com/v1/token. STS checks the issuer, applies the provider's attribute mapping, evaluates its attribute condition and returns a federated token, exposed as the auth_token output.
Then it writes gha-creds-*.json into $GITHUB_WORKSPACE and exports GOOGLE_APPLICATION_CREDENTIALS and CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE pointing at it. The file holds the URL and bearer token for requesting a GitHub OIDC token, not a token itself, so a library can fetch a fresh GitHub token and repeat the exchange. With a service_account, the file also carries an impersonation URL, and the library calls IAM Credentials generateAccessToken when it first needs a token. That is why an impersonation failure usually surfaces in a later step as Unable to acquire impersonated credentials, not in the auth step.
Two ordering rules follow from the file. actions/checkout must run first, because it would delete the file. And gsutil does not read it; use gcloud storage.
jobs:
deploy:
runs-on: ubuntu-latest
permissions:
contents: read
id-token: write
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
- uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
with:
project_id: my-project
workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github/providers/my-repo
- run: gcloud storage ls gs://my-bucketThe pool, the provider and the attribute condition
Create a pool, then an OIDC provider in it with issuer https://token.actions.githubusercontent.com. Every claim you want to test must be mapped to an attribute first, and Google says that for GitHub you must add an attribute condition restricting tokens to your organization, because GitHub uses one issuer for every organization.
Google also recommends the numeric repository_owner_id and repository_id over the names, since a deleted organization or repository name can be claimed by someone else. Mapped attributes are limited to 127 bytes, and pool, provider and IAM changes can take up to 5 minutes to propagate.
gcloud iam workload-identity-pools create github \
--project=my-project --location=global
gcloud iam workload-identity-pools providers create-oidc my-repo \
--project=my-project --location=global \
--workload-identity-pool=github \
--issuer-uri=https://token.actions.githubusercontent.com \
--attribute-mapping="google.subject=assertion.sub,attribute.repository=assertion.repository,attribute.repository_owner_id=assertion.repository_owner_id" \
--attribute-condition="assertion.repository_owner_id == '1342004'"Direct federation or through a service account
Since v2 service_account is optional, and leaving it out selects Direct Workload Identity Federation. The trade-offs, from the README:
| Direct | Through a service account | credentials_json | |
|---|---|---|---|
| Who gets IAM roles | principalSet://iam.googleapis.com/<pool>/attribute.repository/<org/repo> | The service account; the principalSet gets roles/iam.workloadIdentityUser on it | The service account |
access_token or id_token outputs | No | Yes | Yes, with Token Creator on itself |
| Service support | Only services that accept principalSet identities | Services that accept a service account | Services that accept a service account |
| Long-lived secret | None | None | A key that does not expire by default |
token_format and the outputs
By default the action generates no token beyond the federated one. Set token_format: access_token and it impersonates the service account inside the step and outputs an OAuth 2.0 token, which is what docker login to Artifact Registry needs with the username oauth2accesstoken. access_token_lifetime defaults to 3600s and cannot exceed one hour unless the constraints/iam.allowServiceAccountCredentialLifetimeExtension policy allows up to 12. token_format: id_token needs id_token_audience, and ID tokens last 10 minutes.
Outputs: project_id, credentials_file_path, auth_token (always), access_token and id_token (only for their format). Asking for a token also moves the impersonation error into the auth step, which makes it easier to read.
- id: auth
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
with:
token_format: access_token
workload_identity_provider: projects/123456789/locations/global/workloadIdentityPools/github/providers/my-repo
service_account: deployer@my-project.iam.gserviceaccount.com
- uses: docker/login-action@8aa5831fb8d48d8f76594c7ddde61ae58019f550 # v4.8.0
with:
registry: us-docker.pkg.dev
username: oauth2accesstoken
password: ${{ steps.auth.outputs.access_token }}The errors, and which leg raised them
Everything the action raises is prefixed google-github-actions/auth failed with: . Read the next clause: it names the leg. workload_identity_provider and credentials_json are mutually exclusive, and the action fails if you pass both or neither. With credentials_json, minify the key to one line before storing it, because GitHub masks a secret line by line. The audience leg has its own page: workload identity audience errors.
| Message | Leg | Check |
|---|---|---|
GitHub Actions did not inject $ACTIONS_ID_TOKEN_REQUEST_TOKEN or $ACTIONS_ID_TOKEN_REQUEST_URL into this job. | Token request | id-token: write, or a fork pull request |
Failed to generate Google Cloud federated token for //iam.googleapis.com/projects/... | STS | Provider name with the project number, attribute condition, mapping, casing |
Failed to generate Google Cloud OAuth 2.0 Access Token for <service account> | Impersonation, in the step | roles/iam.workloadIdentityUser for the right principalSet |
Unable to acquire impersonated credentials in a later step | Impersonation, deferred | The same binding; this is the library reporting it |
The size of mapped attribute exceeds the 127 bytes limit. | STS | A long repository or branch name in a mapped claim |
The access token lifetime cannot exceed 3600 seconds. | Impersonation | Clock skew; try access_token_lifetime: 3300s |
Key takeaways
- The auth step always runs the STS exchange; impersonation runs in the step only with
token_format. - Google requires an attribute condition for GitHub providers; prefer
repository_owner_idover names. workload_identity_providertakes the project number, and the full provider name, not the pool.- Check out before auth, and use
gcloud storage, notgsutil.
Frequently asked questions
Should I use direct Workload Identity Federation or a service account?
principalSet identities and you do not need an access token output; the README prefers it. Use a service account when you need token_format (for a registry login, say) or a service that does not support principal sets.Why does gcloud fail with "Unable to acquire impersonated credentials" after the auth step passed?
token_format, the auth step only runs the STS leg and writes a file; gcloud performs the impersonation later and gets the 403. Grant the provider's principalSet roles/iam.workloadIdentityUser on the service account, then add token_format: access_token if you want the failure reported in the auth step instead.Why is gsutil not authenticated after google-github-actions/auth?
gsutil does not use the credentials the action exports. Switch the commands to gcloud storage, which reads CLOUDSDK_AUTH_CREDENTIAL_FILE_OVERRIDE.What is the audience for google-github-actions/auth?
https://iam.googleapis.com/ plus the full provider name, which is what a provider with default allowed audiences expects. Set audience only if you configured custom allowed audiences on the provider.Related guides
References
- google-github-actions/auth README and action.yml at v3.0.0 (read 2026-11-12)
- google-github-actions/auth docs/TROUBLESHOOTING.md
- google-github-actions/auth src/client/workload_identity_federation.ts at v3.0.0
- Google Cloud: Workload Identity Federation with deployment pipelines
- GitHub docs: OpenID Connect
- GitHub Actions documentation
- Workflow syntax for GitHub Actions