{"record":{"id":"289bcac043a7a941","repo":"vxcontrol/pentagi","slug":"email-is-empty-in-google-id-token-claims","errorCode":null,"errorMessage":"email is empty in Google ID Token claims","messagePattern":"email is empty in Google ID Token claims","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/server/oauth/google.go","lineNumber":54,"sourceCode":"\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,\n\t\tRedirectURL:  redirectURL,\n\t\tScopes: []string{\n\t\t\t\"https://www.googleapis.com/auth/userinfo.email\",\n\t\t\t\"openid\",\n\t\t},\n\t\tEndpoint: google.Endpoint,\n\t}, newGoogleEmailResolver(clientID))\n}","sourceCodeStart":36,"sourceCodeEnd":72,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/oauth/google.go#L36-L72","documentation":"Thrown when the verified Google ID Token contains no email claim (claims.Email == \"\") after nonce validation succeeded. PentAGI keys user identity on email, so a token without an email cannot create or match a user account. Google omits the email claim when the account has no primary email or the app did not request email scopes.","triggerScenarios":"Google OAuth callback where the ID token passed verification and nonce checks but carries an empty email — the OAuth client lacks the userinfo.email / openid scopes, or the Google account has no email to disclose, or email scope consent was not granted.","commonSituations":"Workspace accounts where the admin disabled email sharing; apps whose Google Cloud OAuth consent screen doesn't include the email scope; users who declined the email permission during consent; using a service account or restricted test account without a primary email.","solutions":["Confirm the client requests scopes \"openid\" and \"https://www.googleapis.com/auth/userinfo.email\" (see NewGoogleOAuthClient) — add them if missing.","Update the Google Cloud OAuth consent screen to include the email scope and re-publish.","Test with a personal Google account that has a primary email to rule out Workspace restrictions.","Have the affected user re-consent (revoke app access at myaccount.google.com/permissions and log in again).","If emails legitimately may be absent, fall back to the userinfo endpoint (GET userinfo.email profile) using the access token."],"exampleFix":"// before\nScopes: []string{\n    \"https://www.googleapis.com/auth/userinfo.email\",\n    \"openid\",\n},\n// after (unchanged scopes, but add a fallback when email is absent)\nif claims.Email == \"\" {\n    return \"\", false, fmt.Errorf(\"email is empty in Google ID Token claims: re-consent with email scope required\")\n}","handlingStrategy":"validation","validationCode":"// check scopes before initiating the OAuth flow\nrequired := []string{\"openid\", \"https://www.googleapis.com/auth/userinfo.email\"}\nfor _, r := range required {\n    if !slices.Contains(cfg.Scopes, r) {\n        return fmt.Errorf(\"missing required scope: %s\", r)\n    }\n}","typeGuard":"func hasEmailClaim(claims googleTokenClaims) bool {\n    return claims.Email != \"\" && strings.Contains(claims.Email, \"@\")\n}","tryCatchPattern":"email, verified, err := resolver.Resolve(ctx, nonce, token)\nif err != nil {\n    if strings.Contains(err.Error(), \"email is empty\") {\n        return fmt.Errorf(\"Google account did not share an email; re-consent with the email scope\")\n    }\n    return err\n}","preventionTips":["Request both openid and userinfo.email scopes in the OAuth client","Ensure the Google consent screen includes the email scope","Have users revoke and re-grant consent when scopes change","Provide a fallback (userinfo endpoint) if email may be absent"],"tags":["oauth","google","oidc","email","scopes"],"backgroundTag":"missing-email-claim","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}