Skip to content
Latchkey LogoLatchkey home

google-github-actions/auth workload identity audience errors

A google-github-actions/auth workload identity audience failure is the first leg of the login being refused, inside the step itself, when Google's token service declines to exchange your GitHub token. The action reports the operation it was performing, which is what separates this from everything that happens afterwards.

Two different audience values in one Google Cloud login, and where each of them is set
The audience input sets only the claim requested from GitHub. The value sent to Google is built separately in the client as two slashes, the IAM host and the provider name.

What this error means

The auth step is red and nothing after it ran. The message begins with the action naming itself, then describes a failure to generate a federated token, then lists key and value pairs: the operation, an endpoint class, a status, an error class and an attempt counter. There is no Google service account in it and no IAM role, because the request never got as far as one.

Shape assembled from the throw in src/client/workload_identity_federation.ts
Error: google-github-actions/auth failed with: Failed to generate Google Cloud federated token: operation=token_exchange, endpoint_class=sts.googleapis.com, status=400, error_class=invalid_request, attempt=3/3

There are two audiences and they are not the same string

This is the detail that makes the error hard to reason about. The action computes one audience in main.ts for the token it requests from GitHub: the audience input if you set one, otherwise https://iam.googleapis.com/ followed by your provider resource name. It computes a second one inside the federation client for the exchange request body, as two slashes, then the IAM host, then the provider resource name.

So the audience input changes only the claim inside the GitHub token. The value Google is asked to validate against is derived from workload_identity_provider regardless. Setting the input to something Google does not expect, or setting it at all when it was not needed, is a common way to break a configuration that was working, and it is invisible unless you know the two values are computed in different places.

Common causes

The provider resource name uses the project ID instead of the project number

The most common configuration error, and it fails at this leg because the named provider does not resolve. The path is projects, then the number, then locations, then the pool and the provider. A path that reads correctly to a human and uses the ID is wrong.

The provider's attribute condition rejects this repository

A condition restricting the provider to particular repositories or owners is doing its job when it refuses, and the refusal looks identical to a misconfiguration. A condition that references an attribute the mapping never defines fails for a different reason and is worth checking at the same time.

The audience input was set and did not need to be

Overriding the audience changes the claim GitHub puts in the token while leaving the value sent to Google derived from the provider. Unless the provider was configured to expect that exact custom audience, the two no longer agree. Removing the input is often the repair.

The job never obtained an OIDC token to exchange

Worth ruling out first, because it produces a different message. Without id-token: write the action fails before the exchange, complaining about the request URL rather than about a federated token. If the message names the token exchange, a GitHub token was obtained and the permission is not the issue.

How to fix it

Confirm which leg failed before touching IAM

  1. If the message contains operation=token_exchange, the failure is the first leg and no service account was involved.
  2. If it names an impersonated credential instead, the exchange succeeded and the problem is downstream.
  3. If it mentions the OIDC request URL, no GitHub token was minted and the job permission is the issue.

Rebuild the provider path from the project number

Take the number from the project, not the identifier, and construct the full resource path. This single value is the most frequent cause and the easiest to verify.

.github/workflows/deploy.yml (illustrative)
      - uses: google-github-actions/auth@v2
        with:
          workload_identity_provider: projects/123456789012/locations/global/workloadIdentityPools/github/providers/my-repo
          service_account: deployer@my-project.iam.gserviceaccount.com

Remove the audience input unless the provider expects a custom one

The default is derived from the provider you already named, so leaving the input unset keeps the two values consistent by construction. Set it only when the provider was deliberately configured with a non-default allowed audience, and then set both sides together.

Read the attribute condition against a real token's claims

The condition is evaluated against the claims in the GitHub token, so it can only reference attributes the mapping defines. Check that every attribute the condition uses is mapped, and that the values it compares against match the repository actually running the workflow.

Reading the key and value pairs

The action serializes the failure rather than narrating it, which is more useful than it looks once you know what each field is.

Field in the messageWhat it tells you
operation=token_exchangeThe first leg. Impersonation has not been attempted and is not implicated
endpoint_classWhich Google host answered, confirming the request left the runner
statusThe HTTP status from the token service, or none when nothing answered
error_classThe classification the action assigned, which decides whether it retried
attempt=n/mHow many times it tried; a full count means the failure was judged permanent or persistent

What the token service is actually checking

The exchange asks Google to accept a JWT issued by GitHub as proof of identity. Google validates it against the workload identity provider named in the request: that the issuer is GitHub, that the audience matches what the provider expects, and that the provider's attribute condition is satisfied by the claims in the token. Any of those failing produces a refusal at this stage, before any service account is considered.

The provider resource name is where most configurations go wrong, because it must use the project number rather than the project ID. The two look interchangeable in a console and are not, and a provider path built with the ID names a resource that does not exist, which fails here rather than anywhere more informative.

Why there is no recorded run on this page

The exchange is a conversation between GitHub's OIDC provider and a Google project's token service about a provider resource we would have to create. Our pool, our attribute mapping and our attribute condition would decide the outcome, and the fields in the message that matter, the status and the error class, are answers about our configuration rather than yours. What is stable and worth reading once is where each of the two audience values is computed, and that is in the action's source, which we read instead.

How to prevent it

  • Store the provider resource path as a single configuration value, so the project number cannot drift back to an identifier.
  • Leave the audience unset unless the provider requires a custom one, and change both together when it does.
  • Keep the attribute mapping and the attribute condition in one place, since the condition can only reference what the mapping defines.
  • Grant id-token: write on the job, so a permission problem never masquerades as an exchange problem.

Frequently asked questions

Does the audience input control what Google validates?
Only indirectly. It sets the audience requested from GitHub for the token itself. The audience field in the exchange request is built separately inside the action's federation client from the IAM host and the provider resource name. Changing the input alone can therefore make the two disagree.
What does status=none mean?
That no HTTP response was classified, so Google's token service did not answer in a way the action could read. That points at reaching the endpoint rather than at your provider configuration, and none of the audience or attribute settings are implicated. A numeric status means the service answered and declined.
Why does the project number matter so much?
Because the provider resource path is addressed by number, and a path built with the project ID names something that does not exist. The failure surfaces at the exchange rather than anywhere that mentions projects, which is why the value is worth checking before any IAM binding.
How do I tell this apart from an impersonation failure?
By which step is red and by the wording. This failure happens inside the auth step and names the token exchange operation. An impersonation failure happens after the auth step has succeeded, is raised by a Google client library rather than by the action, and carries a Google response body describing a service account.

Related guides

References

Actions mints the token, not the machine. Latchkey runs the rest at $0.0025/min at 2 vCPU against $0.006. Start free → 30-day trial · No credit card