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

  1. Repeat the OAuth flow so both tokens come from the same, current token response.
  2. Verify no code path caches an ID token or access token across logins and pairs them later.
  3. Check any proxy or middleware for token rewriting/truncation between Google and your backend.
  4. 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

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


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/54e0db4842aaaab5. Report an issue: GitHub.