{"record":{"id":"4cb55409ac2805bd","repo":"Kareadita/Kavita","slug":"errors-oidc-email-not-verified","errorCode":null,"errorMessage":"errors.oidc.email-not-verified","messagePattern":"errors\\.oidc\\.email-not-verified","errorType":"exception","errorClass":"KavitaException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/OidcService.cs","lineNumber":92,"sourceCode":"        }\n\n        var user = await unitOfWork.UserRepository.GetByOidcId(oidcId, AppUserIncludes.UserPreferences | AppUserIncludes.SideNavStreams, ct);\n        if (user != null)\n        {\n            await SyncUserSettings(request, settings, principal, user);\n\n            return user;\n        }\n\n        var email = principal.FindFirstValue(ClaimTypes.Email);\n        if (string.IsNullOrEmpty(email))\n        {\n            throw new KavitaException(\"errors.oidc.missing-email\");\n        }\n\n        if (settings.RequireVerifiedEmail && !principal.HasVerifiedEmail())\n        {\n            throw new KavitaException(\"errors.oidc.email-not-verified\");\n        }\n\n\n        user = await unitOfWork.UserRepository.GetUserByEmailAsync(email, AppUserIncludes.UserPreferences | AppUserIncludes.SideNavStreams, ct);\n        if (user != null)\n        {\n            // Don't allow taking over accounts\n            // This could happen if the user changes their email in OIDC, and then someone else uses the old one\n            if (!string.IsNullOrEmpty(user.OidcId))\n            {\n                throw new KavitaException(\"errors.oidc.email-in-use\");\n            }\n\n            logger.LogDebug(\"User {UserName} has matched on email to {OidcId}\", user.Id, oidcId);\n            user.OidcId = oidcId;\n            await unitOfWork.CommitAsync(ct);\n\n            await SyncUserSettings(request, settings, principal, user);","sourceCodeStart":74,"sourceCodeEnd":110,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/OidcService.cs#L74-L110","documentation":"Thrown when settings.RequireVerifiedEmail is true and principal.HasVerifiedEmail() returns false. Kavita refuses to provision/link an account from an OIDC identity whose email is unverified, to prevent account takeover via an unverified email. Only evaluated on the new-user / email-match path, after the email is confirmed present.","triggerScenarios":"First-time OIDC login (or email-match to an existing local account) where RequireVerifiedEmail=true in OIDC settings but the token lacks the 'email_verified' claim or it's false.","commonSituations":"Admin enabled RequireVerifiedEmail but the IdP doesn't issue the email_verified claim or marks it false; user's email in the IdP is pending verification; test/dev IdP that omits the claim.","solutions":["Have the user verify their email in the identity provider, then re-login.","If the IdP doesn't emit email_verified, either disable RequireVerifiedEmail or configure the IdP to include a true email_verified claim.","Decode the IdToken to confirm the 'email_verified' claim value.","Re-check the OIDC settings RequireVerifiedEmail toggle matches your security policy."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"if (settings.RequireVerifiedEmail && !principal.HasVerifiedEmail())\n    return Challenge(\"Email not verified by identity provider.\");","typeGuard":"bool EmailIsVerifiedForPolicy(ClaimsPrincipal p, OidcConfigDto s) =>\n    !s.RequireVerifiedEmail || p.HasVerifiedEmail();","tryCatchPattern":"try { var user = await oidcService.LoginOrCreate(Request, principal, ct); }\ncatch (KavitaException ex) when (ex.Message == \"errors.oidc.email-not-verified\")\n{ return Challenge(\"Please verify your email with your provider.\"); }","preventionTips":["Only enable RequireVerifiedEmail when the IdP issues email_verified.","Have users verify email in the IdP before first login.","Decode the IdToken to confirm email_verified=true during setup."],"tags":["oidc","authentication","email-verification","security"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}