OrchardCMS/OrchardCore · error · InvalidOperationException

No suitable user name can be found in the principal.

Error message

No suitable user name can be found in the principal.

What it means

OpenIdExtensions.GetUserName extracts a user name from a ClaimsPrincipal, checking OpenID Connect 'name' (Claims.Name) first, then ClaimTypes.Name. If neither claim exists on the principal it throws InvalidOperationException because downstream code (e.g. mapping external logins to a local user) requires a displayable name.

Solutions

  1. Request the 'profile' (and 'openid') scopes on the OpenIdConnect client options so the name claim is issued
  2. Configure ClaimActions/claim-type mappings so the provider's display-name claim maps to ClaimTypes.Name
  3. Use GetUserName on principals known to carry name claims, or fall back to the 'sub'/NameIdentifier claim for user lookup
  4. Wrap in try-catch and handle missing-name principals explicitly (e.g. prompt user for a username)

Example fix

// before
var name = principal.GetUserName();
// after
var name = principal.FindFirst(Claims.Name)?.Value
    ?? principal.FindFirst(ClaimTypes.Name)?.Value
    ?? principal.FindFirst(ClaimTypes.NameIdentifier)?.Value
    ?? throw new InvalidOperationException("No suitable user name can be found in the principal.");
Defensive patterns

Strategy: validation

Validate before calling

if (principal?.Identity?.IsAuthenticated != true ||
    (principal.FindFirst(ClaimTypes.Name) is null && principal.FindFirst("name") is null)) {
    throw new InvalidOperationException("Principal has no name claim; request 'profile' scope or map claims first.");
}

Try / catch

try { name = principal.GetUserName(); } catch (InvalidOperationException ex) { logger.LogWarning(ex, "Principal missing name claim"); /* fallback to subject claim */ }

Prevention

When it happens

Trigger: Calling GetUserName on a principal whose identity token contains no 'name' or 'nameidentifier'-style Name claim — e.g. an OpenID Connect provider that only sends 'sub', or a token missing the 'profile'/'email' scopes that populate name claims.

Common situations: Configuring an external OIDP (Keycloak, Auth0, Azure AD) without requesting the profile scope; a misconfigured claim-type mapping so the provider's name claim isn't mapped to ClaimTypes.Name; custom tokens issued without standard claims.

Related errors


AI-assisted analysis of OrchardCMS/OrchardCore@4306c0717f (2026-09-13). Data as JSON: /api/errors/18f89eb4fa1a48e1. Report an issue: GitHub.

Appendix: source

Thrown at src/OrchardCore.Modules/OrchardCore.OpenId/OpenIdExtensions.cs:23

internal static class OpenIdExtensions
{
    internal static string GetUserIdentifier(this ClaimsIdentity identity)
        => identity.FindFirst(Claims.Subject)?.Value ??
           identity.FindFirst(ClaimTypes.NameIdentifier)?.Value ??
           identity.FindFirst(ClaimTypes.Upn)?.Value ??
           throw new InvalidOperationException("No suitable user identifier can be found in the identity.");

    internal static string GetUserIdentifier(this ClaimsPrincipal principal)
        => principal.FindFirst(Claims.Subject)?.Value ??
           principal.FindFirst(ClaimTypes.NameIdentifier)?.Value ??
           principal.FindFirst(ClaimTypes.Upn)?.Value ??
           throw new InvalidOperationException("No suitable user identifier can be found in the principal.");

    internal static string GetUserName(this ClaimsPrincipal principal)
        => principal.FindFirst(Claims.Name)?.Value ??
           principal.FindFirst(ClaimTypes.Name)?.Value ??
           throw new InvalidOperationException("No suitable user name can be found in the principal.");

    internal static string[] GetRoles(this ClaimsPrincipal principal)
        => principal.FindAll(c => c.Type is Claims.Role or ClaimTypes.Role)
        .Select(x => x.Value)
        .Distinct()
        .ToArray();

    internal static async Task<bool> AnyAsync<T>(this IAsyncEnumerable<T> source)
    {
        ArgumentNullException.ThrowIfNull(source);

        await using var enumerator = source.GetAsyncEnumerator();
        return await enumerator.MoveNextAsync();
    }

    internal static Task<List<T>> ToListAsync<T>(this IAsyncEnumerable<T> source)
    {
        ArgumentNullException.ThrowIfNull(source);

View on GitHub (pinned to 4306c0717f)