googleapis / googleapis/google-api-go-client

impersonate: JWT exchanged with legacy grant_type=assertion, now rejected with "invalid_grant: Invalid JWT Signature"

Open Beginner friendly
#3,671 0 comments 0 reactions 0 assignees View on GitHub
priority: p2 type: bug
Dominant language
Go
Stars
4.5k
Forks
1.5k
Avg merge
1d 15h
Merged PRs (30d)
32

Description

#### Environment details

- Programming language: Go
- OS: Linux and macOS (also reproducible with plain curl, see below)
- Language runtime version: go1.23
- Package version: v0.283.0 (the relevant code is unchanged on master)

#### TL;DR

`impersonate.CredentialsTokenSource` with `Subject` set signs a JWT via
`iamcredentials.signJwt` and then exchanges it at `https://oauth2.googleapis.com/token`
using the pre-RFC 7523 legacy grant:

https://github.com/googleapis/google-api-go-client/blob/v0.283.0/impersonate/user.go#L144-L148

Since around 2026-07-16, that exchange fails with
`{"error":"invalid_grant","error_description":"Invalid JWT Signature."}` for JWTs signed
with recently-rotated system-managed keys. The exact same JWT is accepted when exchanged
with `grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer`, which is what
google-auth-library-python uses for this same flow
([`_client.py`](https://github.com/googleapis/google-auth-library-python/blob/main/google/oauth2/_client.py),
[`impersonated_credentials.py`](https://github.com/googleapis/google-auth-library-python/blob/main/google/auth/impersonated_credentials.py)).

Google rotates system-managed keys every ~2 weeks, so every user of this code path
breaks as soon as their SA's key rotates — with no client-side change involved. We hit
this through oauth2-proxy (it uses this package for Google Groups authorization) in four
unrelated GCP projects: in each one, failures started right after that SA's key
rotation, intermittent at first, 100% within a week. Config had been untouched for
months, and we verified every other hop (STS, `generateAccessToken`, `signJwt`) works.

#### Steps to reproduce

Any SA you can call `signJwt` on works; a freshly-created SA reproduces it immediately
since its key is new.

```bash
SA=@.iam.gserviceaccount.com
NOW=$(date +%s)
cat > claims.json < {"error":"invalid_grant","error_description":"Invalid JWT Signature."} (HTTP 400)

# same JWT, RFC 7523 grant:
curl -s https://oauth2.googleapis.com/token \
-d grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer \
--data-urlencode "assertion=$(cat signed.jwt)"
# -> {"access_token":"ya29...","expires_in":3599,"token_type":"Bearer"} (HTTP 200)
```

The only difference between the two requests is the grant format; the JWT is
byte-for-byte the same and was signed by Google minutes earlier, so this isn't a
malformed assertion or a key problem on the caller's side.

#### Expected behavior

The exchange should use the RFC 7523 grant like the Python library does. It's a
two-line change in `exchangeToken` — happy to send a PR.

Note that `cloud.google.com/go/auth` has the same legacy grant in its equivalent flow
([user.go](https://github.com/googleapis/google-cloud-go/blob/main/auth/credentials/impersonate/user.go)),
so it needs the same fix.

Is the legacy `grant_type=assertion` path being turned down server-side? Its validator
seems to no longer pick up newly-rotated keys, which is what makes the error so
misleading. If there's a decommission timeline it would be good to know, since affected
users will keep trickling in as their keys rotate.

Workaround meanwhile: use a SA JSON key (`golang.org/x/oauth2/jwt` uses the modern
grant), or drop `Subject` if plain impersonation works for your case.

Contributor guide

Open the contributing guide

Research direction

Start in impersonate/user.go at exchangeToken and compare its token request with the RFC 7523 request shown in the reproduction and the Python library references. Verify the legacy grant is replaced for the Subject flow, then rerun the curl reproduction to confirm the exchanged JWT is accepted.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
authentication
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
68/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.