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
- Add 'openid' to the scope list in the oidc section of headscale.yaml
- Verify the IdP client is an OpenID Connect client and can issue ID tokens (test with an OIDC debugger flow)
- 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
- Always include 'openid' in oidc.scope
- Smoke-test the IdP with an OIDC flow that returns an id_token before pointing headscale at it
- Never replay authorization codes during debugging
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
- oidc state parameter is too short
- empty OIDC callback params
- registration info not in cache
- authenticated principal is not in any allowed group
- oidc_client_secret and oidc_client_secret_path are mutually
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/43ba6bbded6d3c42.
Report an issue: GitHub.