vxcontrol/pentagi · error
failed to verify Google Access Token: %w
Error message
failed to verify Google Access Token: %w
What it means
idToken.VerifyAccessToken (backend/pkg/server/oauth/google.go:41) checks the ID token's at_hash claim against the actual OAuth access token. If they don't match, the access token was not the one issued together with this ID token, and the resolver rejects the pair with this error.
Source
Thrown at backend/pkg/server/oauth/google.go:41
}
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")
}
if claims.Email == "" {
return "", false, fmt.Errorf("email is empty in Google ID Token claims")
}
return claims.Email, claims.EmailVerified, nil
}
}View on GitHub (pinned to ea665308ba)
Solutions
- Repeat the OAuth flow so both tokens come from the same, current token response.
- Verify no code path caches an ID token or access token across logins and pairs them later.
- Check any proxy or middleware for token rewriting/truncation between Google and your backend.
- If it persists, log token metadata (not values) on both sides to identify which component alters the token.
Defensive patterns
Strategy: validation
Validate before calling
// the at_hash check runs inside the library; guard by only pairing tokens from one response:
tok, err := conf.Exchange(ctx, code)
if err != nil {
return err
}
if _, ok := tok.Extra("id_token").(string); !ok {
return fmt.Errorf("refusing to proceed: id_token missing from the same token response as the access token")
} Try / catch
email, verified, err := googleEmailResolver(ctx, nonce, token)
if err != nil {
if strings.Contains(err.Error(), "failed to verify Google Access Token") {
return "", false, fmt.Errorf("token pair rejected (access token does not match ID token); restart sign-in")
}
return "", false, err
} Prevention
- Only use the ID token and access token returned together in a single token response.
- Never cache ID tokens across login sessions or mix them with refreshed access tokens.
- Ensure no middleware or proxy rewrites or truncates token values.
- Add an integration test that performs a full Google OAuth round-trip.
When it happens
Trigger: VerifyAccessToken(token.AccessToken) returns an error: at_hash mismatch with the supplied access token, or an at_hash algorithm mismatch for the signing algorithm of the ID token.
Common situations: Access token and ID token assembled from different exchanges (e.g. ID token cached from a previous login); middleware or logging layers truncating/normalizing the access token; token-response mixing behind an OAuth proxy.
Related errors
- could not verify Google ID 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/54e0db4842aaaab5.
Report an issue: GitHub.