{"record":{"id":"789b6f35da25278f","repo":"juanfont/headscale","slug":"authenticated-principal-is-not-in-any-allowed-grou","errorCode":null,"errorMessage":"authenticated principal is not in any allowed group","messagePattern":"authenticated principal is not in any allowed group","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/oidc.go","lineNumber":53,"sourceCode":"\n\t// cookieNamePrefixLen is the number of leading characters from a\n\t// state/nonce value that [getCookieName] splices into the cookie name.\n\t// State and nonce values that are shorter than this are rejected at\n\t// the callback boundary so [getCookieName] cannot panic on a slice\n\t// out-of-range.\n\tcookieNamePrefixLen = 6\n)\n\nvar errOIDCStateTooShort = errors.New(\"oidc state parameter is too short\")\n\nvar (\n\terrEmptyOIDCCallbackParams = errors.New(\"empty OIDC callback params\")\n\terrNoOIDCIDToken           = errors.New(\"extracting ID token\")\n\terrNoOIDCRegistrationInfo  = errors.New(\"registration info not in cache\")\n\terrOIDCAllowedDomains      = errors.New(\n\t\t\"authenticated principal does not match any allowed domain\",\n\t)\n\terrOIDCAllowedGroups = errors.New(\"authenticated principal is not in any allowed group\")\n\terrOIDCAllowedUsers  = errors.New(\n\t\t\"authenticated principal does not match any allowed user\",\n\t)\n\terrOIDCUnverifiedEmail = errors.New(\"authenticated principal has an unverified email\")\n\terrInvalidPKCEMethod   = errors.New(\"invalid pkce.method\")\n)\n\n// AuthInfo contains both auth ID and verifier information for OIDC validation.\ntype AuthInfo struct {\n\tAuthID       types.AuthID\n\tVerifier     *string\n\tRegistration bool\n}\n\ntype AuthProviderOIDC struct {\n\th         *Headscale\n\tserverURL string\n\tcfg       *types.OIDCConfig","sourceCodeStart":35,"sourceCodeEnd":71,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/oidc.go#L35-L71","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"# before\noidc:\n  allowed_groups: [\"Engineering\"]\n\n# after (match the exact claim value)\noidc:\n  allowed_groups: [\"engineering\", \"idp-engineering\"]","handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["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"],"tags":["oidc","authorization","config","headscale"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}