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
- Ensure the identity includes a 'sub' (Claims.Subject) or ClaimTypes.NameIdentifier claim before calling GetUserIdentifier.
- If building the principal yourself, add identity.AddClaim(new Claim(Claims.Subject, userId)) (and NameIdentifier).
- Check the user service / external identity provider mapping so the subject claim is populated during authentication.
- 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
- Always add a 'sub' claim when constructing ClaimsIdentity instances used with OpenID flows.
- Map NameIdentifier in external authentication handlers (OnCreatingTicket).
- Unit-test principal builders to assert a subject claim exists.
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
- No suitable user identifier can be found in the principal.
- No suitable user name can be found in the principal.
- An error occurred while pruning authorizations.
- Multiple certificates with the same thumbprint were found.
- The application details cannot be found.
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)