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
- Restart the login flow from /register so a fresh state+verifier pair is cached
- Complete the IdC login within register_cache_expiration (raise it if users are slow)
- 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
- Complete login within register_cache_expiration
- Use sticky routing when running multiple headscale replicas with PKCE
- Avoid triggering >1024 concurrent pending registrations (cache cap)
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
- oidc state parameter is too short
- empty OIDC callback params
- extracting ID token
- registration expired
- pkce.method must be either 'plain' or 'S256'
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/89e289862a07542e.
Report an issue: GitHub.