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

  1. Add "openid" (plus "email" and "profile") to the OAuth2 scopes used to start the flow.
  2. Ensure the token passed to the resolver came from the initial code exchange, not a refresh grant.
  3. Log the raw token response fields to confirm id_token is present; check any proxy in front of the token endpoint.
  4. 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

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


AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01). Data as JSON: /api/errors/d647340c9274a9b8. Report an issue: GitHub.