Kareadita/Kavita · error · KavitaException

errors.oidc.failed-to-update-username

Error message

errors.oidc.failed-to-update-username

What it means

Thrown by OidcService.SyncUsername when userManager.SetUserNameAsync(user, bestName) returns Succeeded=false. FindBestAvailableName selected a candidate name from the token claims (preferred_username, name, given_name, or surname), and the name passed the IsNameAvailable check, but Identity's username validator rejected the final SetUserNameAsync call. This can happen when IdentityOptions.User.AllowedUserNameCharacters or a custom IUserValidator rejects characters in the name.

Source

Thrown at Kavita.Services/OidcService.cs:503

        }
        catch (Exception)
        {
            /* Swallow exception */
        }

    }

    private async Task SyncUsername(ClaimsPrincipal claimsPrincipal, AppUser user)
    {
        var bestName = await FindBestAvailableName(claimsPrincipal, user.UserName);
        if (bestName == null || bestName == user.UserName) return;

        var res = await userManager.SetUserNameAsync(user, bestName);
        if (!res.Succeeded)
        {
            logger.LogError("Failed to update username for user {UserId} to {NewUserName} from OIDC {Errors}", user.Id,
                bestName.Censor(),  res.Errors.Select(x => x.Description).ToList());
            throw new KavitaException("errors.oidc.failed-to-update-username");
        }
    }

    private async Task SyncRoles(OidcConfigDto settings, ClaimsPrincipal claimsPrincipal, AppUser user)
    {
        var rolesFromToken = claimsPrincipal.GetClaimsWithPrefix(settings.RolesClaim, settings.RolesPrefix);

        var roles = PolicyConstants.ValidRoles
            .Where(s => rolesFromToken.Contains(s, StringComparer.OrdinalIgnoreCase))
            .ToList();

        // Ensure that Admin Role and ReadOnly aren't both selected
        if (roles.Contains(PolicyConstants.AdminRole))
        {
            roles = roles.Where(r => r !=  PolicyConstants.ReadOnlyRole).ToList();
        }

        logger.LogDebug("Syncing access roles for user {UserId}, found roles {Roles}", user.Id, roles);

View on GitHub (pinned to 9c3e540000)

Solutions

  1. Check Kavita logs for 'Failed to update username for user {UserId} to {NewUserName} from OIDC {Errors}' — the IdentityError descriptions reveal which character or rule was violated.
  2. Adjust the IdP claim to use a username with allowed characters (alphanumeric, dash, underscore, dot by default).
  3. If unicode usernames are desired, extend IdentityOptions.User.AllowedUserNameCharacters in Kavita's startup configuration to include the needed character ranges.
  4. Ensure Kavita's AccountService.ValidateUsername and Identity's default username validator agree on allowed characters to avoid the gap where one passes and the other fails.
  5. Disable username syncing in OIDC settings if the IdP usernames are incompatible with Kavita's constraints.

Example fix

// In Startup/Program configuration, widen allowed characters:
services.Configure<IdentityOptions>(opt =>
{
    opt.User.AllowedUserNameCharacters =
        "abcdefghijklmnopqrstuvwxyz" +
        "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
        "0123456789-._@+" + // add needed chars
        "\u00e0\u00e1\u00e2"; // unicode example (add more ranges as needed)
});

// Or disable username sync in OIDC settings to avoid the conflict entirely.
Defensive patterns

Strategy: validation

Validate before calling

// Pre-validate the candidate username against Identity's rules:
// var name = await oidcService.FindBestAvailableName(principal, user.UserName);
// if (name != null && name != user.UserName)
// {
//     var result = await userManager.UserValidator.ValidateAsync(userManager, user with { UserName = name });
//     if (!result.Succeeded)
//         LogWarning("Username '{Name}' will be rejected by Identity", name);
// }

Try / catch

// try { await oidcService.SyncUserSettings(...); }
// catch (KavitaException ex) when (ex.Message.Contains("failed-to-update-username"))
// {
//     // Username sync failed; login continues with existing username.
//     // Check logs for the IdentityError with character violations.
//     logger.LogWarning("Username sync failed for user {UserId}", user.Id);
// }

Prevention

When it happens

Trigger: The IdP token contains a preferred_username or name claim with characters not in IdentityOptions.User.AllowedUserNameCharacters (default is a restrictive ASCII set). FindBestAvailableName returns the name because IsNameAvailable (which calls ValidateUsername) didn't flag it, but SetUserNameAsync uses a different validation path that rejects it.

Common situations: The IdP username contains spaces, unicode characters, or special symbols (e.g., 'José', 'user@host', '名前'). Kavita's default AllowedUserNameCharacters excludes these. A custom username validator has stricter rules than ValidateUsername in AccountService, creating a gap where IsNameAvailable passes but SetUserNameAsync fails.

Related errors


AI-assisted analysis of Kareadita/Kavita@9c3e540000 (2026-08-13). Data as JSON: /api/errors/7f0e8b83b8004456. Report an issue: GitHub.