OrchardCMS/OrchardCore · error · InvalidOperationException

No suitable user identifier can be found in the identity.

Error message

No suitable user identifier can be found in the identity.

What it means

OpenIdExtensions.GetUserIdentifier(ClaimsIdentity) resolves a stable user identifier by trying Claims.Subject, then ClaimTypes.NameIdentifier, then ClaimTypes.Upn. If the identity contains none of these claims, the null-coalescing chain ends in an InvalidOperationException('No suitable user identifier can be found in the identity.').

Solutions

  1. Ensure the identity includes a 'sub' (Claims.Subject) or ClaimTypes.NameIdentifier claim before calling GetUserIdentifier.
  2. If building the principal yourself, add identity.AddClaim(new Claim(Claims.Subject, userId)) (and NameIdentifier).
  3. Check the user service / external identity provider mapping so the subject claim is populated during authentication.
  4. Wrap the call and fall back to another identifying claim if your scenario genuinely lacks a subject.

Example fix

// before
var identity = new ClaimsIdentity(authType);
// after
var identity = new ClaimsIdentity(authType);
identity.AddClaim(new Claim(Claims.Subject, user.UserId));
Defensive patterns

Strategy: type-guard

Validate before calling

if (identity.FindFirst(Claims.Subject) == null &&
    identity.FindFirst(ClaimTypes.NameIdentifier) == null &&
    identity.FindFirst(ClaimTypes.Upn) == null)
{
    throw new InvalidOperationException("Identity lacks a subject claim; cannot resolve user identifier.");
}

Type guard

bool HasUserIdentifier(ClaimsIdentity identity) =>
    identity.FindFirst(Claims.Subject)?.Value != null ||
    identity.FindFirst(ClaimTypes.NameIdentifier)?.Value != null ||
    identity.FindFirst(ClaimTypes.Upn)?.Value != null;

Try / catch

try
{
    userId = identity.GetUserIdentifier();
}
catch (InvalidOperationException ex) when (ex.Message.Contains("user identifier"))
{
    logger.LogWarning(ex, "No subject claim on identity; rejecting request");
    return Forbid();
}

Prevention

When it happens

Trigger: Code (e.g. AccessController token exchange or authorization flows) calls identity.GetUserIdentifier() on an identity created from a token or ticket that lacks 'sub', nameidentifier, and UPN claims.

Common situations: Custom ClaimsIdentity construction in tests or extensions without a subject claim; tokens issued with suppressed subject claims; user service that builds principals without NameIdentifier; upstream identity provider configured to omit 'sub'.

Related errors


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

Appendix: source

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

using System.Security.Claims;
using static OpenIddict.Abstractions.OpenIddictConstants;

namespace OrchardCore.OpenId;

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();

View on GitHub (pinned to 4306c0717f)