vxcontrol/pentagi · error
could not verify Google ID Token: %w
Error message
could not verify Google ID Token: %w
What it means
The resolver verifies the ID token's signature, issuer, audience, and expiry via provider.Verifier(...).Verify (backend/pkg/server/oauth/google.go:33). Any failure — bad signature, wrong client_id audience, expired token, malformed JWT — is wrapped as this error and the login is rejected.
Source
Thrown at backend/pkg/server/oauth/google.go:33
EmailVerified bool `json:"email_verified"`
}
func newGoogleEmailResolver(clientID string) OAuthEmailResolver {
return func(ctx context.Context, nonce string, token *oauth2.Token) (string, bool, error) {
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
return "", false, fmt.Errorf("could not create Google OpenID client: %w", err)
}
oidToken, ok := token.Extra("id_token").(string)
if !ok {
return "", false, fmt.Errorf("id_token is not present in the token")
}
verifier := provider.Verifier(&oidc.Config{ClientID: clientID})
idToken, err := verifier.Verify(ctx, oidToken)
if err != nil {
return "", false, fmt.Errorf("could not verify Google ID Token: %w", err)
}
if idToken.Nonce != nonce {
return "", false, fmt.Errorf("nonce mismatch in Google ID Token")
}
if err = idToken.VerifyAccessToken(token.AccessToken); err != nil {
return "", false, fmt.Errorf("failed to verify Google Access Token: %w", err)
}
claims := googleTokenClaims{}
if err := idToken.Claims(&claims); err != nil {
return "", false, fmt.Errorf("failed to parse Google ID Token claims: %w", err)
}
if claims.Nonce != nonce {
return "", false, fmt.Errorf("nonce mismatch in Google ID Token claims")
}View on GitHub (pinned to ea665308ba)
Solutions
- Confirm the clientID passed to the verifier matches the OAuth client that issued the token (same Google Cloud project).
- Check server clock synchronization (NTP) to avoid spurious expiry/issuance-time failures.
- Have the user retry the login to obtain a fresh token if it simply expired mid-flow.
- Log the verification error with errors.Unwrap to distinguish audience vs signature vs expiry causes.
Defensive patterns
Strategy: try-catch
Validate before calling
// verify the token's audience claim matches the configured clientID before deeper checks
idToken, err := verifier.Verify(ctx, oidToken)
if err != nil {
return fmt.Errorf("ID token rejected (check clientID, clock skew, expiry): %w", err)
} Try / catch
email, verified, err := googleEmailResolver(ctx, nonce, token)
if err != nil {
if strings.Contains(err.Error(), "could not verify Google ID Token") {
return "", false, fmt.Errorf("sign-in rejected: ID token invalid or issued for a different OAuth client")
}
return "", false, err
} Prevention
- Keep the clientID configured for the verifier identical to the OAuth client that starts the flow.
- Run NTP time sync on the server to avoid spurious expiry errors.
- Never share OAuth client credentials between staging and production.
- Log errors.Unwrap(err) to distinguish audience vs signature vs expiry failures.
When it happens
Trigger: verifier.Verify returns an error: the id_token's aud does not equal the configured clientID, the token is expired or issued in the future (clock skew), the signature does not match Google's discovered keys, or the JWT is malformed.
Common situations: clientID/secret from one Google Cloud project used against tokens issued for another; server clock drift making valid tokens appear expired; swapping OAuth client credentials between environments (staging vs prod); corrupted or hand-modified tokens.
Related errors
- failed to verify Google Access Token: %w
- id_token is not present in the token
- nonce mismatch in Google ID Token
- failed to parse Google ID Token claims: %w
- could not create Google OpenID client: %w
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/372d616fa583e199.
Report an issue: GitHub.