juanfont/headscale · error

registration info not in cache

Error message

registration info not in cache

What it means

Returned by getOauth2Token (hscontrol/oidc.go:473-476) when PKCE is enabled and the callback 'state' is not found in the in-memory auth cache that stores the PKCE verifier. HTTP 404 'registration not found'. The cache is per-process and bounded (authCacheMaxEntries = 1024).

Source

Thrown at hscontrol/oidc.go:49

	// 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. Restart the login flow from /register so a fresh state+verifier pair is cached
  2. Complete the IdC login within register_cache_expiration (raise it if users are slow)
  3. With multiple replicas, pin callbacks to the instance that started the flow (sticky routing) — the cache is not shared
Defensive patterns

Strategy: retry

Try / catch

if httpErr, ok := err.(*HTTPError); ok && errors.Is(httpErr.err, errNoOIDCRegistrationInfo) { redirect user to /register to mint a fresh state+verifier }

Prevention

When it happens

Trigger: PKCE enabled (oidc.pkce.enabled) and the state is absent from the auth cache: session older than register_cache_expiration (default 15m), headscale restarted between /register and callback, cache eviction under heavy registration volume, or multiple headscale replicas without sticky routing.

Common situations: User sits on the IdP login page past the cache expiry; headscale redeploy/restart mid-login; load-balanced control planes where the callback lands on a different instance; scripted load tests minting >1024 pending registrations.

Related errors


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