juanfont/headscale · error
authenticated principal is not in any allowed group
Error message
authenticated principal is not in any allowed group
What it means
Returned by the OIDC group-authorization check (hscontrol/oidc.go:541) when allowed_groups is configured but the authenticated principal's groups (from the ID token or userinfo) intersect none of them. HTTP 401 'unauthorised group'. This is an intentional access denial, not a malfunction.
Source
Thrown at hscontrol/oidc.go:53
// 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
}
type AuthProviderOIDC struct {
h *Headscale
serverURL string
cfg *types.OIDCConfigView on GitHub (pinned to 565fd254d0)
Solutions
- Decode the ID token (jwt.io or 'headscale' debug logs) and confirm the actual group strings and claim name
- Set oidc.allowed_groups to the exact group values the IdP emits, or map the claim via your IdP
- Add the user to an allowed group in the IdP, or relax the list if the restriction is unintended
Example fix
# before oidc: allowed_groups: ["Engineering"] # after (match the exact claim value) oidc: allowed_groups: ["engineering", "idp-engineering"]
Defensive patterns
Strategy: validation
Prevention
- Decode a sample ID token and copy group strings verbatim into allowed_groups
- Confirm the groups claim name matches what headscale reads
- Keep an admin user exempt from group restrictions during rollout
When it happens
Trigger: oidc.allowed_groups set in headscale.yaml and the user's token carries a groups claim that matches none of the configured entries (exact string match).
Common situations: Group claim name mismatch: headscale reads 'groups' by default (configurable), while IdPs may emit 'roles' or 'memberOf'; group strings differ in case or path form (e.g. 'engineering' vs 'idp://engineering'); user genuinely not in any allowed group; allowed_groups set without allowed_domains fallback.
Understand the failure class
- Authentication and authorization failures — expired tokens, bad credentials, and missing scopes.
Related errors
- extracting ID token
- authenticated principal has an unverified email
- oidc_client_secret and oidc_client_secret_path are mutually
- oidc.issuer must be a valid http(s) URL
- oidc.client_id is required when oidc.issuer is set
AI-assisted analysis of juanfont/headscale@565fd254d0 (2026-08-15).
Data as JSON: /api/errors/789b6f35da25278f.
Report an issue: GitHub.