Kareadita/Kavita · error · KavitaException
errors.oidc.email-in-use
Error message
errors.oidc.email-in-use
What it means
Thrown when an OIDC login matches an existing Kavita user by email, but that user already has a non-empty OidcId — i.e. the email belongs to an account already linked to a different OIDC identity. Kavita blocks the takeover: it won't reassign the account to a new oidcId. This protects against email reuse after an IdP-side email change.
Source
Thrown at Kavita.Services/OidcService.cs:103
if (string.IsNullOrEmpty(email))
{
throw new KavitaException("errors.oidc.missing-email");
}
if (settings.RequireVerifiedEmail && !principal.HasVerifiedEmail())
{
throw new KavitaException("errors.oidc.email-not-verified");
}
user = await unitOfWork.UserRepository.GetUserByEmailAsync(email, AppUserIncludes.UserPreferences | AppUserIncludes.SideNavStreams, ct);
if (user != null)
{
// Don't allow taking over accounts
// This could happen if the user changes their email in OIDC, and then someone else uses the old one
if (!string.IsNullOrEmpty(user.OidcId))
{
throw new KavitaException("errors.oidc.email-in-use");
}
logger.LogDebug("User {UserName} has matched on email to {OidcId}", user.Id, oidcId);
user.OidcId = oidcId;
await unitOfWork.CommitAsync(ct);
await SyncUserSettings(request, settings, principal, user);
return user;
}
return await CreateNewAccount(request, principal, settings, oidcId);
}
public async Task<AppUser?> RefreshCookieToken(CookieValidatePrincipalContext ctx, CancellationToken ct = default)
{
if (ctx.Principal == null) return null;
View on GitHub (pinned to 9c3e540000)
Solutions
- If this is the legitimate same user, clear or update user.OidcId to the new oidcId (admin action) then re-login.
- If it's a different person, they must use a distinct email or a different account; do not merge blindly.
- Audit the existing user's OidcId/IdentityProvider to confirm which IdP owns the account.
- Ensure each user has a unique email across all linked IdPs to avoid future collisions.
Defensive patterns
Strategy: try-catch
Validate before calling
var existing = await unitOfWork.UserRepository.GetUserByEmailAsync(email, includes, ct);
if (existing != null && !string.IsNullOrEmpty(existing.OidcId) && existing.OidcId != oidcId)
return Conflict("An account already exists for this email under a different identity."); Try / catch
try { var user = await oidcService.LoginOrCreate(Request, principal, ct); }
catch (KavitaException ex) when (ex.Message == "errors.oidc.email-in-use")
{ return Conflict("Email already linked to another OIDC account."); } Prevention
- Keep each user's email unique across linked IdPs.
- When a user changes their IdP email, update their Kavita OidcId, don't create a new one.
- Audit users with non-empty OidcId before bulk migrations.
When it happens
Trigger: User A registered via OIDC with oidcId X and email E. Someone now logs in with a different oidcId Y but the same email E (e.g. E was reassigned, or a second IdP shares the address). GetUserByEmailAsync finds user A, sees OidcId X already set, and throws.
Common situations: User changed their email in the IdP; admin manually set an OidcId on a local account; two different IdP accounts share an email; leftover OidcId from a previous provider after migration.
Related errors
- errors.oidc.email-not-verified
- User is not authenticated
- errors.oidc.no-account
- errors.oidc.missing-external-id
- errors.oidc.missing-email
AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13).
Data as JSON: /api/errors/0f3542201f3c7923.
Report an issue: GitHub.