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

  1. Configure the IdP to emit at least one recognized user identifier claim (sub for OIDC, persistent NameID for SAML, or a custom attribute).
  2. If using a non-standard attribute, add it to the SSO config's 'additional user ID claim types' list.
  3. For SAML providers sending transient NameIDs, ensure the 'uid' or another attribute contains the persistent identifier.
  4. 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

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


AI-assisted analysis of bitwarden/server@e93b962371 (2026-08-13). Data as JSON: /api/errors/7ab146e5a49ad34c. Report an issue: GitHub.