bitwarden/server · error · Exception
UnknownUserId
Error message
UnknownUserId
What it means
Thrown in AccountController.FindUserFromExternalProviderAsync (line 491) as a throw expression on the claim-resolution chain. Bitwarden tries to find a unique user identifier from the IdP using multiple claim types in priority order: custom types from SsoConfigData, then sub (JwtClaimTypes.Subject), then a non-transient NameIdentifier, then 'uid', 'upn', 'eppn'. If none are found, this error is thrown.
Source
Thrown at bitwarden_license/src/Sso/Controllers/AccountController.cs:491
static bool nameIdIsNotTransient(Claim c) => c.Type == ClaimTypes.NameIdentifier
&& (c.Properties == null
|| !c.Properties.TryGetValue(SamlPropertyKeys.ClaimFormat,
out var claimFormat)
|| claimFormat != SamlNameIdFormats.Transient);
// Try to determine the unique id of the external user (issued by the provider)
// the most common claim type for that are the sub claim and the NameIdentifier
// depending on the external provider, some other claim type might be used
var customUserIdClaimTypes = ssoConfigData.GetAdditionalUserIdClaimTypes();
var userIdClaim = externalUser.FindFirst(c => customUserIdClaimTypes.Contains(c.Type)) ??
externalUser.FindFirst(JwtClaimTypes.Subject) ??
externalUser.FindFirst(nameIdIsNotTransient) ??
// Some SAML providers may use the `uid` attribute for this
// where a transient NameID has been sent in the subject
externalUser.FindFirst("uid") ??
externalUser.FindFirst("upn") ??
externalUser.FindFirst("eppn") ??
throw new Exception(_i18nService.T("UnknownUserId"));
#nullable restore
// Remove the user id claim so we don't include it as an extra claim if/when we provision the user
var claims = externalUser.Claims.ToList();
claims.Remove(userIdClaim);
// find external user
var providerUserId = userIdClaim.Value;
var possibleSsoUser = await _userRepository.GetBySsoUserAsync(providerUserId, orgId);
return (possibleSsoUser, provider, providerUserId, claims, ssoConfigData);
}
/// <summary>
/// This function seeks to set up the org user record or create a new user record based on the conditions
/// below.
///View on GitHub (pinned to e93b962371)
Solutions
- Configure the IdP to emit at least one recognized user identifier claim (sub for OIDC, persistent NameID for SAML, or a custom attribute).
- If using a non-standard attribute, add it to the SSO config's 'additional user ID claim types' list.
- For SAML providers sending transient NameIDs, ensure the 'uid' or another attribute contains the persistent identifier.
- Inspect the actual claims returned by the IdP (enable claim logging) to identify what is available.
Defensive patterns
Strategy: validation
Validate before calling
// Pre-check that at least one user-id claim is present
var customTypes = ssoConfigData.GetAdditionalUserIdClaimTypes();
var hasUserId = externalUser.FindFirst(c => customTypes.Contains(c.Type)) != null
|| externalUser.FindFirst(JwtClaimTypes.Subject) != null
|| externalUser.FindFirst(nameIdIsNotTransient) != null
|| externalUser.FindFirst("uid") != null
|| externalUser.FindFirst("upn") != null
|| externalUser.FindFirst("eppn") != null;
if (!hasUserId)
return BadRequest("IdP did not provide a recognized user identifier claim."); Type guard
static bool HasValidUserIdClaim(ClaimsPrincipal principal, IEnumerable<string> customTypes)
{
return principal.HasClaim(c => customTypes.Contains(c.Type))
|| principal.HasClaim(JwtClaimTypes.Subject)
|| principal.HasClaim(c => c.Type == ClaimTypes.NameIdentifier
&& (c.Properties == null
|| !c.Properties.TryGetValue(SamlPropertyKeys.ClaimFormat, out var fmt)
|| fmt != SamlNameIdFormats.Transient))
|| principal.HasClaim("uid")
|| principal.HasClaim("upn")
|| principal.HasClaim("eppn");
} Try / catch
try { await FindUserFromExternalProviderAsync(result); }
catch (Exception ex) when (ex.Message.Contains("UnknownUserId"))
{ /* log claims received; instruct admin to configure IdP attributes */ } Prevention
- Configure the IdP to emit a persistent, unique user identifier (sub for OIDC, persistent NameID for SAML).
- If using a non-standard attribute, add it to the SSO config's additional user ID claim types.
- Log received claims during SSO testing to verify IdP attribute mapping.
When it happens
Trigger: The external user's claims principal contains no claim matching any of the searched types (custom user-id claim types, sub, non-transient NameIdentifier, uid, upn, eppn).
Common situations: IdP is not configured to emit any of the standard user-identifier claims; SAML provider sends only a transient NameID with no fallback attribute; claim type mapping in the SSO config's additional user ID claim types is wrong; IdP was reconfigured to omit previously-present claims.
Related errors
- AcrMissingOrInvalid
- ExternalAuthenticationError
- CannotFindEmailClaim
- NoDomainHintProvided
- InvalidSsoToken
AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13).
Data as JSON: /api/errors/7ab146e5a49ad34c.
Report an issue: GitHub.