{"record":{"id":"d647340c9274a9b8","repo":"vxcontrol/pentagi","slug":"id-token-is-not-present-in-the-token","errorCode":null,"errorMessage":"id_token is not present in the token","messagePattern":"id_token is not present in the token","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/server/oauth/google.go","lineNumber":27,"sourceCode":"\t\"golang.org/x/oauth2/google\"\n)\n\ntype googleTokenClaims struct {\n\tNonce         string `json:\"nonce\"`\n\tEmail         string `json:\"email\"`\n\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 {","sourceCodeStart":9,"sourceCodeEnd":45,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/server/oauth/google.go#L9-L45","documentation":"After exchanging the OAuth code, the resolver reads the id_token field from the oauth2 token (backend/pkg/server/oauth/google.go:27). If the token response contains no id_token string, the type assertion fails and this error aborts login, because the resolver's identity proof is exactly that ID token.","triggerScenarios":"Google's token response lacking id_token — typically when the OAuth config's scopes do not include \"openid\", or when a token obtained from a non-authorization-code flow (e.g. refresh) is passed to the resolver.","commonSituations":"OAuth app configured with only profile/email scopes and no openid scope; mixing token responses from refresh-token exchanges; custom token endpoint or proxy returning a trimmed response body.","solutions":["Add \"openid\" (plus \"email\" and \"profile\") to the OAuth2 scopes used to start the flow.","Ensure the token passed to the resolver came from the initial code exchange, not a refresh grant.","Log the raw token response fields to confirm id_token is present; check any proxy in front of the token endpoint.","Redirect the user through the full authorization flow again with corrected scopes."],"exampleFix":"// before\nconf.Scopes = []string{\"profile\", \"email\"}\n\n// after\nconf.Scopes = []string{\"openid\", \"profile\", \"email\"}","handlingStrategy":"validation","validationCode":"// ensure the OAuth2 config requests an ID token at all\nconf.Scopes = []string{\"openid\", \"profile\", \"email\"}\n\n// before resolving, confirm the token carries an id_token\nif _, ok := token.Extra(\"id_token\").(string); !ok {\n    return fmt.Errorf(\"token response has no id_token; check scopes include openid and that this is the initial code exchange\")\n}","typeGuard":null,"tryCatchPattern":"email, verified, err := googleEmailResolver(ctx, nonce, token)\nif err != nil {\n    if strings.Contains(err.Error(), \"id_token is not present\") {\n        return \"\", false, fmt.Errorf(\"login misconfigured: restart authorization with openid scope\")\n    }\n    return \"\", false, err\n}","preventionTips":["Always include the openid scope in Google OAuth2 scopes.","Only pass tokens from the initial code exchange (not refresh grants) to the resolver.","Assert id_token presence in integration tests for the OAuth callback.","Keep a single shared OAuth2 config so scopes never drift between entry points."],"tags":["oauth","oidc","google","jwt"],"backgroundTag":"missing-id-token","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}