{"record":{"id":"2da6cbe826885481","repo":"vxcontrol/pentagi","slug":"failed-to-parse-google-id-token-claims-w","errorCode":null,"errorMessage":"failed to parse Google ID Token claims: %w","messagePattern":"failed to parse Google ID Token claims: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/server/oauth/google.go","lineNumber":46,"sourceCode":"\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}\n\nfunc NewGoogleOAuthClient(clientID, clientSecret, redirectURL string) OAuthClient {\n\treturn NewOAuthClient(\"google\", &oauth2.Config{\n\t\tClientID:     clientID,\n\t\tClientSecret: clientSecret,","sourceCodeStart":28,"sourceCodeEnd":64,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/oauth/google.go#L28-L64","documentation":"Thrown in newGoogleEmailResolver when idToken.Claims(&claims) fails to unmarshal the verified Google ID Token payload into googleTokenClaims (nonce, email, email_verified). With go-oidc v3 this almost always means the ID token payload is not valid JSON or the json.Unmarshal into the claims struct failed, which is practically impossible for a genuinely verified Google token — so it usually indicates a corrupted, truncated, or substituted id_token string, or a custom/misrouted OIDC provider whose claims shape differs.","triggerScenarios":"Google OAuth callback processed via AuthAuthorize: the id_token passed verifier.Verify, VerifyAccessToken succeeded, but Claims() cannot decode the token payload into the googleTokenClaims struct (malformed JSON payload, wrong token supplied as id_token, or a proxy/MTM returning mangled token bodies).","commonSituations":"An HTTP proxy or service mesh (MITM) altering response bodies; a misconfigured OIDC base URL pointing at a non-Google issuer that issues tokens without standard claim JSON; token string truncated by logging/rewriting middleware; storing/replaying the token through something that corrupts it (e.g. HTML-escaped state).","solutions":["Log the raw id_token (first ~50 chars) and run it through jwt.io / a JWKS decode to inspect the payload for corruption.","Verify no proxy or middleware rewrites token response bodies (check HTTP_PROXY, ingress, service mesh MITM settings).","Ensure the clientID passed to oidc.Config matches the OAuth client that issued the token.","Update github.com/coreos/go-oidc/v3 to the latest version; very old versions had stricter claim decoding behavior.","If the issuer is intentionally not Google, use the matching provider discovery URL instead of hard-coded accounts.google.com."],"exampleFix":"// before\nclaims := googleTokenClaims{}\nif err := idToken.Claims(&claims); err != nil {\n    return \"\", false, fmt.Errorf(\"failed to parse Google ID Token claims: %w\", err)\n}\n// after\nclaims := googleTokenClaims{}\nif err := idToken.Claims(&claims); err != nil {\n    return \"\", false, fmt.Errorf(\"failed to parse Google ID Token claims: %w (raw token prefix: %.40s)\", err, oidToken)\n}","handlingStrategy":"try-catch","validationCode":"const raw = token.Extra(\"id_token\")\nif s, ok := raw.(string); !ok || s == \"\" {\n    return fmt.Errorf(\"id_token missing or not a string\")\n}\nparts := strings.Split(s, \".\")\nif len(parts) != 3 {\n    return fmt.Errorf(\"id_token is not a JWT\")\n}\nif payload, err := base64.RawURLEncoding.DecodeString(parts[1]); err != nil || !json.Valid(payload) {\n    return fmt.Errorf(\"id_token payload is not valid JSON\")\n}","typeGuard":"func hasValidIDToken(t *oauth2.Token) bool {\n    s, ok := t.Extra(\"id_token\").(string)\n    return ok && strings.Count(s, \".\") == 2\n}","tryCatchPattern":"email, verified, err := resolver.Resolve(ctx, nonce, token)\nif err != nil {\n    if strings.Contains(err.Error(), \"failed to parse Google ID Token claims\") {\n        logger.WithError(err).Error(\"corrupt or non-standard Google ID token payload; inspect raw token and proxy config\")\n        return fmt.Errorf(\"oauth login failed: malformed ID token\")\n    }\n    return err\n}","preventionTips":["Always verify the JWT structure (3 dot-separated segments) before exchange debugging","Check for proxies/middleware that rewrite token response bodies","Keep github.com/coreos/go-oidc/v3 up to date","Log a truncated token prefix on failure for diagnosis"],"tags":["oauth","google","oidc","jwt"],"backgroundTag":"id-token-claims-parse-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}