{"record":{"id":"faa22b41ec43d651","repo":"Kareadita/Kavita","slug":"errors-oidc-failed-to-update-email","errorCode":null,"errorMessage":"errors.oidc.failed-to-update-email","messagePattern":"errors\\.oidc\\.failed-to-update-email","errorType":"exception","errorClass":"KavitaException","httpStatus":null,"severity":"error","filePath":"Kavita.Services/OidcService.cs","lineNumber":443,"sourceCode":"        {\n            throw new KavitaException(\"errors.oidc.email-not-verified\");\n        }\n\n        // Ensure no other user uses this email\n        var other = await userManager.FindByEmailAsync(email);\n        if (other != null)\n        {\n            throw new KavitaException(\"errors.oidc.email-in-use\");\n        }\n\n        // The email is verified, we can go ahead and change & confirm it\n        if (claimsPrincipal.HasVerifiedEmail())\n        {\n            var res = await userManager.SetEmailAsync(user, email);\n            if (!res.Succeeded)\n            {\n                logger.LogError(\"Failed to update email for user {UserId} from OIDC {Errors}\", user.Id, res.Errors.Select(x => x.Description).ToList());\n                throw new KavitaException(\"errors.oidc.failed-to-update-email\");\n            }\n\n            user.EmailConfirmed = true;\n            await userManager.UpdateAsync(user);\n            return;\n        }\n\n        var token = await userManager.GenerateEmailConfirmationTokenAsync(user);\n        var isValidEmailAddress = !string.IsNullOrEmpty(user.Email) && emailService.IsValidEmail(user.Email);\n        var isEmailSetup = (await unitOfWork.SettingsRepository.GetSettingsDtoAsync()).IsEmailSetup();\n        var shouldEmailUser = isEmailSetup || !isValidEmailAddress;\n\n        user.EmailConfirmed = !shouldEmailUser;\n        user.ConfirmationToken = token;\n        await userManager.UpdateAsync(user);\n\n        var emailLink = await emailService.GenerateEmailLink(request, user.ConfirmationToken, \"confirm-email-update\", email);\n        logger.LogCritical(\"[Update Email]: Automatic email update after OIDC sync, email Link for {UserId}: {Link}\", user.Id, emailLink);","sourceCodeStart":425,"sourceCodeEnd":461,"githubUrl":"https://github.com/Kareadita/Kavita/blob/9c3e5400007f8a0282f7d883f2ad5e71716e514d/Kavita.Services/OidcService.cs#L425-L461","documentation":"Thrown by OidcService.SyncEmail when ASP.NET Identity's userManager.SetEmailAsync(user, email) returns a result with Succeeded=false. This indicates the Identity layer rejected the email change — typically due to a validation failure in a custom IUserValidator, an email-format rule, or an Identity configuration constraint. The detailed errors are logged with the user ID but not surfaced to the user beyond this generic message.","triggerScenarios":"The email passed all prior checks (verified, not in use by another user), but userManager.SetEmailAsync returns IdentityResult with one or more IdentityError objects. This can happen if a custom user validator rejects the email format, if IdentityOptions.User settings impose restrictions, or if a DB unique constraint fires at the EF level and surfaces through Identity.","commonSituations":"A custom IUserValidator<AppUser> is registered that enforces domain allow-lists or format rules stricter than the default. The database has a manual unique index on the Email column that conflicts with EF's expectation. A race condition where another user's email was committed between the FindByEmailAsync check and the SetEmailAsync call.","solutions":["Check Kavita logs for the line 'Failed to update email for user {UserId} from OIDC {Errors}' — the Errors list contains the IdentityError descriptions that explain the rejection.","Address the specific Identity error (e.g., if a custom validator rejected the domain, adjust the validator or the email).","If the error is a DB constraint violation, check for duplicate Email rows and clean them up.","Ensure no custom IUserValidator or IdentityOptions configuration is interfering with email assignment.","Retry the login after resolving the underlying validation issue; the email sync will re-attempt on next OIDC login."],"exampleFix":"// No code fix for the caller — root cause is in Identity configuration.\n// Check logs for the IdentityError descriptions:\n//   \"Failed to update email for user 42 from OIDC [\"Email domain not allowed\"]\n// Then either fix the validator or the email value.\n\n// If you have a custom validator, ensure it allows valid IdP emails:\nservices.Configure<IdentityOptions>(o =>\n{\n    o.User.RequireUniqueEmail = true; // already the default\n    // remove any overly restrictive custom validators\n});","handlingStrategy":"try-catch","validationCode":"// Pre-validate that the email will pass Identity validation:\n// var emailValidator = serviceProvider.GetRequiredService<IUserValidator<AppUser>>();\n// var result = await emailValidator.ValidateAsync(userManager, user with { Email = newEmail });\n// if (!result.Succeeded)\n//     LogWarning(\"Email will be rejected by Identity: {Errors}\",\n//         result.Errors.Select(e => e.Description));","typeGuard":null,"tryCatchPattern":"// try { await oidcService.SyncUserSettings(...); }\n// catch (KavitaException ex) when (ex.Message.Contains(\"failed-to-update-email\"))\n// {\n//     // Check logs for IdentityError descriptions.\n//     // Email sync failed but login may continue.\n//     logger.LogWarning(\"Email sync rejected by Identity for user {UserId}\", user.Id);\n// }","preventionTips":["Ensure no custom IUserValidator imposes rules stricter than the IdP's email format.","Keep the database's Email column unique-index in sync with EF Core's configuration.","Check logs for the 'Failed to update email' line with IdentityError descriptions after OIDC login failures.","Test email sync in staging with the production IdP's actual email claims.","Avoid manual DB edits to the Email column that could cause constraint violations."],"tags":["oidc","email","aspnet-identity","validation","configuration"],"backgroundTag":null,"analyzedSha":"9c3e5400007f8a0282f7d883f2ad5e71716e514d","analyzedAt":"2026-08-13T19:06:05.897Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}