{"record":{"id":"372d616fa583e199","repo":"vxcontrol/pentagi","slug":"could-not-verify-google-id-token-w","errorCode":null,"errorMessage":"could not verify Google ID Token: %w","messagePattern":"could not verify Google ID Token: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/server/oauth/google.go","lineNumber":33,"sourceCode":"\tEmailVerified bool   `json:\"email_verified\"`\n}\n\nfunc newGoogleEmailResolver(clientID string) OAuthEmailResolver {\n\treturn func(ctx context.Context, nonce string, token *oauth2.Token) (string, bool, error) {\n\t\tprovider, err := oidc.NewProvider(ctx, \"https://accounts.google.com\")\n\t\tif err != nil {\n\t\t\treturn \"\", false, fmt.Errorf(\"could not create Google OpenID client: %w\", err)\n\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}","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/oauth/google.go#L15-L51","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// verify the token's audience claim matches the configured clientID before deeper checks\nidToken, err := verifier.Verify(ctx, oidToken)\nif err != nil {\n    return fmt.Errorf(\"ID token rejected (check clientID, clock skew, expiry): %w\", err)\n}","typeGuard":null,"tryCatchPattern":"email, verified, err := googleEmailResolver(ctx, nonce, token)\nif err != nil {\n    if strings.Contains(err.Error(), \"could not verify Google ID Token\") {\n        return \"\", false, fmt.Errorf(\"sign-in rejected: ID token invalid or issued for a different OAuth client\")\n    }\n    return \"\", false, err\n}","preventionTips":["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."],"tags":["oauth","oidc","google","jwt","security"],"backgroundTag":"jwt-verification-failed","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}