{"record":{"id":"2c6a5a531e29666e","repo":"juanfont/headscale","slug":"authenticated-principal-has-an-unverified-email","errorCode":null,"errorMessage":"authenticated principal has an unverified email","messagePattern":"authenticated principal has an unverified email","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"hscontrol/oidc.go","lineNumber":57,"sourceCode":"\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\n\n\t// authCache holds auth information between the auth and the callback\n\t// steps. It is a bounded [expirable.LRU] keyed by OIDC state, evicting oldest\n\t// entries to keep the cache footprint constant under attack.","sourceCodeStart":39,"sourceCodeEnd":75,"githubUrl":"https://github.com/juanfont/headscale/blob/565fd254d06c4c7f9a8cad1714a43445c79ba420/hscontrol/oidc.go#L39-L75","documentation":"Returned by the OIDC email verification check (hscontrol/oidc.go:586) when the authenticated principal's email has email_verified=false (or the flag is absent and force_verified_email is enabled). HTTP 401 'unverified email'. Headscale refuses to map an unverified email onto a tailnet identity.","triggerScenarios":"oidc.force_verified_email (or equivalent policy requiring verified email) is on and the IdP reports the user's email as not verified, or the IdP omits the email_verified claim entirely.","commonSituations":"Development IdPs (Keycloak/Dex dev mode) that never mark emails verified; users with pending email confirmation; IdP that does not implement email_verified at all; use_email_as_name or domain restrictions layered on top.","solutions":["Have the user verify their email in the IdP, then retry login","If the IdP cannot provide verification, disable the force-verified-email requirement in headscale.yaml","For dev IdPs, enable the 'email as verified' option (e.g. Keycloak's 'Verify email' off / Dex mock OIDC)"],"exampleFix":null,"handlingStrategy":"validation","validationCode":null,"typeGuard":null,"tryCatchPattern":null,"preventionTips":["Require verified emails at the IdP before enabling the check","For dev IdPs, mark emails verified by default","Document the requirement in user onboarding"],"tags":["oidc","authorization","email","headscale"],"backgroundTag":null,"analyzedSha":"565fd254d06c4c7f9a8cad1714a43445c79ba420","analyzedAt":"2026-08-15T13:12:30.133Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}