{"record":{"id":"cc029964f532f899","repo":"Kareadita/Kavita","slug":"errors-oidc-missing-email","errorCode":null,"errorMessage":"errors.oidc.missing-email","messagePattern":"errors\\.oidc\\.missing-email","errorType":"exception","errorClass":"KavitaException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/OidcService.cs","lineNumber":87,"sourceCode":"\n        var oidcId = principal.FindFirstValue(ClaimTypes.NameIdentifier);\n        if (string.IsNullOrEmpty(oidcId))\n        {\n            throw new KavitaException(\"errors.oidc.missing-external-id\");\n        }\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","sourceCodeStart":69,"sourceCodeEnd":105,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/OidcService.cs#L69-L105","documentation":"Thrown in LoginOrCreate after a new (unknown oidcId) user passes the NameIdentifier check but the principal has no ClaimTypes.Email. Kavita requires an email to either match an existing account or build a new one; without it the flow cannot proceed. The 'email' scope being absent is the usual cause.","triggerScenarios":"OIDC login for a first-time user whose token has no email claim. The check fires only when no existing user matched the oidcId (new user path); returning users with valid oidcId bypass this entirely.","commonSituations":"The 'email' or 'profile' scope was not requested; provider doesn't expose email in tokens (some corporate IdPs); email claim mapped under a different name; user's profile in the IdP has no email set.","solutions":["Add 'email' to the requested OIDC scopes (Kavita's DefaultScopes include it — confirm your config didn't override).","Ensure the user has an email address set in the identity provider's profile.","Map the IdP's email claim to ClaimTypes.Email if it uses a custom name.","Decode the IdToken to confirm the email claim is present and non-empty."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"var email = principal.FindFirstValue(ClaimTypes.Email);\nif (string.IsNullOrEmpty(email))\n    return Challenge(\"OIDC token is missing the email claim.\");","typeGuard":"bool HasOidcEmail(ClaimsPrincipal p) =>\n    !string.IsNullOrWhiteSpace(p.FindFirstValue(ClaimTypes.Email));","tryCatchPattern":"try { var user = await oidcService.LoginOrCreate(Request, principal, ct); }\ncatch (KavitaException ex) when (ex.Message == \"errors.oidc.missing-email\")\n{ return Challenge(); // request 'email' scope }","preventionTips":["Include 'email' (and 'profile') in requested OIDC scopes.","Ensure the user has an email set in the IdP.","Map custom email claims to ClaimTypes.Email."],"tags":["oidc","authentication","claims","email"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}