{"record":{"id":"54e0db4842aaaab5","repo":"vxcontrol/pentagi","slug":"failed-to-verify-google-access-token-w","errorCode":null,"errorMessage":"failed to verify Google Access Token: %w","messagePattern":"failed to verify Google Access Token: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/server/oauth/google.go","lineNumber":41,"sourceCode":"\t\t}\n\n\t\toidToken, ok := token.Extra(\"id_token\").(string)\n\t\tif !ok {\n\t\t\treturn \"\", false, fmt.Errorf(\"id_token is not present in the token\")\n\t\t}\n\n\t\tverifier := provider.Verifier(&oidc.Config{ClientID: clientID})\n\t\tidToken, err := verifier.Verify(ctx, oidToken)\n\t\tif err != nil {\n\t\t\treturn \"\", false, fmt.Errorf(\"could not verify Google ID Token: %w\", err)\n\t\t}\n\n\t\tif idToken.Nonce != nonce {\n\t\t\treturn \"\", false, fmt.Errorf(\"nonce mismatch in Google ID Token\")\n\t\t}\n\n\t\tif err = idToken.VerifyAccessToken(token.AccessToken); err != nil {\n\t\t\treturn \"\", false, fmt.Errorf(\"failed to verify Google Access Token: %w\", err)\n\t\t}\n\n\t\tclaims := googleTokenClaims{}\n\t\tif err := idToken.Claims(&claims); err != nil {\n\t\t\treturn \"\", false, fmt.Errorf(\"failed to parse Google ID Token claims: %w\", err)\n\t\t}\n\n\t\tif claims.Nonce != nonce {\n\t\t\treturn \"\", false, fmt.Errorf(\"nonce mismatch in Google ID Token claims\")\n\t\t}\n\n\t\tif claims.Email == \"\" {\n\t\t\treturn \"\", false, fmt.Errorf(\"email is empty in Google ID Token claims\")\n\t\t}\n\n\t\treturn claims.Email, claims.EmailVerified, nil\n\t}\n}","sourceCodeStart":23,"sourceCodeEnd":59,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/oauth/google.go#L23-L59","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// the at_hash check runs inside the library; guard by only pairing tokens from one response:\ntok, err := conf.Exchange(ctx, code)\nif err != nil {\n    return err\n}\nif _, ok := tok.Extra(\"id_token\").(string); !ok {\n    return fmt.Errorf(\"refusing to proceed: id_token missing from the same token response as the access token\")\n}","typeGuard":null,"tryCatchPattern":"email, verified, err := googleEmailResolver(ctx, nonce, token)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to verify Google Access Token\") {\n        return \"\", false, fmt.Errorf(\"token pair rejected (access token does not match ID token); restart sign-in\")\n    }\n    return \"\", false, err\n}","preventionTips":["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."],"tags":["oauth","oidc","google","security","jwt"],"backgroundTag":"hash-mismatch","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}