vxcontrol/pentagi · error
id_token is not present in the token
Error message
id_token is not present in the token
What it means
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.
Source
Thrown at backend/pkg/server/oauth/google.go:27
"golang.org/x/oauth2/google"
)
type googleTokenClaims struct {
Nonce string `json:"nonce"`
Email string `json:"email"`
EmailVerified bool `json:"email_verified"`
}
func newGoogleEmailResolver(clientID string) OAuthEmailResolver {
return func(ctx context.Context, nonce string, token *oauth2.Token) (string, bool, error) {
provider, err := oidc.NewProvider(ctx, "https://accounts.google.com")
if err != nil {
return "", false, fmt.Errorf("could not create Google OpenID client: %w", err)
}
oidToken, ok := token.Extra("id_token").(string)
if !ok {
return "", false, fmt.Errorf("id_token is not present in the token")
}
verifier := provider.Verifier(&oidc.Config{ClientID: clientID})
idToken, err := verifier.Verify(ctx, oidToken)
if err != nil {
return "", false, fmt.Errorf("could not verify Google ID Token: %w", err)
}
if idToken.Nonce != nonce {
return "", false, fmt.Errorf("nonce mismatch in Google ID Token")
}
if err = idToken.VerifyAccessToken(token.AccessToken); err != nil {
return "", false, fmt.Errorf("failed to verify Google Access Token: %w", err)
}
claims := googleTokenClaims{}
if err := idToken.Claims(&claims); err != nil {View on GitHub (pinned to ea665308ba)
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.
Example fix
// before
conf.Scopes = []string{"profile", "email"}
// after
conf.Scopes = []string{"openid", "profile", "email"} Defensive patterns
Strategy: validation
Validate before calling
// ensure the OAuth2 config requests an ID token at all
conf.Scopes = []string{"openid", "profile", "email"}
// before resolving, confirm the token carries an id_token
if _, ok := token.Extra("id_token").(string); !ok {
return fmt.Errorf("token response has no id_token; check scopes include openid and that this is the initial code exchange")
} Try / catch
email, verified, err := googleEmailResolver(ctx, nonce, token)
if err != nil {
if strings.Contains(err.Error(), "id_token is not present") {
return "", false, fmt.Errorf("login misconfigured: restart authorization with openid scope")
}
return "", false, err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- could not verify Google ID Token: %w
- failed to verify Google Access Token: %w
- failed to parse Google ID Token claims: %w
- could not create Google OpenID client: %w
- nonce mismatch in Google ID Token
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/d647340c9274a9b8.
Report an issue: GitHub.