juanfont/headscale · error

extracting ID token

Error message

extracting ID token

What it means

Returned by getOauth2Token (hscontrol/oidc.go:498) when the OAuth2 token exchange succeeded but the returned token contains no id_token. Headscale's OIDC flow requires an OpenID Connect ID token to identify the principal. HTTP 400 'no id_token'.

Source

Thrown at hscontrol/oidc.go:48

	// authCacheMaxEntries bounds the OIDC state→[AuthInfo] cache to prevent
	// unauthenticated cache-fill DoS via repeated /register/{auth_id} or
	// /auth/{auth_id} GETs that mint OIDC state cookies.
	authCacheMaxEntries = 1024

	// cookieNamePrefixLen is the number of leading characters from a
	// state/nonce value that [getCookieName] splices into the cookie name.
	// State and nonce values that are shorter than this are rejected at
	// the callback boundary so [getCookieName] cannot panic on a slice
	// out-of-range.
	cookieNamePrefixLen = 6
)

var errOIDCStateTooShort = errors.New("oidc state parameter is too short")

var (
	errEmptyOIDCCallbackParams = errors.New("empty OIDC callback params")
	errNoOIDCIDToken           = errors.New("extracting ID token")
	errNoOIDCRegistrationInfo  = errors.New("registration info not in cache")
	errOIDCAllowedDomains      = errors.New(
		"authenticated principal does not match any allowed domain",
	)
	errOIDCAllowedGroups = errors.New("authenticated principal is not in any allowed group")
	errOIDCAllowedUsers  = errors.New(
		"authenticated principal does not match any allowed user",
	)
	errOIDCUnverifiedEmail = errors.New("authenticated principal has an unverified email")
	errInvalidPKCEMethod   = errors.New("invalid pkce.method")
)

// AuthInfo contains both auth ID and verifier information for OIDC validation.
type AuthInfo struct {
	AuthID       types.AuthID
	Verifier     *string
	Registration bool
}

View on GitHub (pinned to 565fd254d0)

Solutions

  1. Add 'openid' to the scope list in the oidc section of headscale.yaml
  2. Verify the IdP client is an OpenID Connect client and can issue ID tokens (test with an OIDC debugger flow)
  3. Ensure the authorization code is exchanged exactly once — a replayed code can yield a token without id_token

Example fix

# before
oidc:
  scope: ["profile", "email"]

# after
oidc:
  scope: ["openid", "profile", "email"]
Defensive patterns

Strategy: validation

Prevention

When it happens

Trigger: The code-for-token exchange at the IdP token endpoint returns an access token only. Typical causes: the 'openid' scope missing from the request/client config, the IdP client not configured as an OIDC client, or an authorization code being reused (some IdPs then return a degraded token).

Common situations: oidc.scope config in headscale.yaml missing 'openid'; IdP application created as plain OAuth2 instead of OIDC; clock skew causing the IdP to silently drop the id_token; replaying a used code during debugging.

Related errors


AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15). Data as JSON: /api/errors/43ba6bbded6d3c42. Report an issue: GitHub.