{"record":{"id":"7ab146e5a49ad34c","repo":"bitwarden/server","slug":"unknownuserid","errorCode":null,"errorMessage":"UnknownUserId","messagePattern":"UnknownUserId","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"bitwarden_license/src/Sso/Controllers/AccountController.cs","lineNumber":491,"sourceCode":"        static bool nameIdIsNotTransient(Claim c) => c.Type == ClaimTypes.NameIdentifier\n                                                     && (c.Properties == null\n                                                         || !c.Properties.TryGetValue(SamlPropertyKeys.ClaimFormat,\n                                                             out var claimFormat)\n                                                         || claimFormat != SamlNameIdFormats.Transient);\n\n        // Try to determine the unique id of the external user (issued by the provider)\n        // the most common claim type for that are the sub claim and the NameIdentifier\n        // depending on the external provider, some other claim type might be used\n        var customUserIdClaimTypes = ssoConfigData.GetAdditionalUserIdClaimTypes();\n        var userIdClaim = externalUser.FindFirst(c => customUserIdClaimTypes.Contains(c.Type)) ??\n                          externalUser.FindFirst(JwtClaimTypes.Subject) ??\n                          externalUser.FindFirst(nameIdIsNotTransient) ??\n                          // Some SAML providers may use the `uid` attribute for this\n                          //    where a transient NameID has been sent in the subject\n                          externalUser.FindFirst(\"uid\") ??\n                          externalUser.FindFirst(\"upn\") ??\n                          externalUser.FindFirst(\"eppn\") ??\n                          throw new Exception(_i18nService.T(\"UnknownUserId\"));\n#nullable restore\n\n        // Remove the user id claim so we don't include it as an extra claim if/when we provision the user\n        var claims = externalUser.Claims.ToList();\n        claims.Remove(userIdClaim);\n\n        // find external user\n        var providerUserId = userIdClaim.Value;\n\n        var possibleSsoUser = await _userRepository.GetBySsoUserAsync(providerUserId, orgId);\n\n        return (possibleSsoUser, provider, providerUserId, claims, ssoConfigData);\n    }\n\n    /// <summary>\n    /// This function seeks to set up the org user record or create a new user record based on the conditions\n    /// below.\n    ///","sourceCodeStart":473,"sourceCodeEnd":509,"githubUrl":"https://github.com/bitwarden/server/blob/e93b962371d80964556f5590c6615f5160a437a1/bitwarden_license/src/Sso/Controllers/AccountController.cs#L473-L509","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// Pre-check that at least one user-id claim is present\nvar customTypes = ssoConfigData.GetAdditionalUserIdClaimTypes();\nvar hasUserId = externalUser.FindFirst(c => customTypes.Contains(c.Type)) != null\n    || externalUser.FindFirst(JwtClaimTypes.Subject) != null\n    || externalUser.FindFirst(nameIdIsNotTransient) != null\n    || externalUser.FindFirst(\"uid\") != null\n    || externalUser.FindFirst(\"upn\") != null\n    || externalUser.FindFirst(\"eppn\") != null;\nif (!hasUserId)\n    return BadRequest(\"IdP did not provide a recognized user identifier claim.\");","typeGuard":"static bool HasValidUserIdClaim(ClaimsPrincipal principal, IEnumerable<string> customTypes)\n{\n    return principal.HasClaim(c => customTypes.Contains(c.Type))\n        || principal.HasClaim(JwtClaimTypes.Subject)\n        || principal.HasClaim(c => c.Type == ClaimTypes.NameIdentifier\n            && (c.Properties == null\n                || !c.Properties.TryGetValue(SamlPropertyKeys.ClaimFormat, out var fmt)\n                || fmt != SamlNameIdFormats.Transient))\n        || principal.HasClaim(\"uid\")\n        || principal.HasClaim(\"upn\")\n        || principal.HasClaim(\"eppn\");\n}","tryCatchPattern":"try { await FindUserFromExternalProviderAsync(result); }\ncatch (Exception ex) when (ex.Message.Contains(\"UnknownUserId\"))\n{ /* log claims received; instruct admin to configure IdP attributes */ }","preventionTips":["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."],"tags":["sso","authentication","claims","idp","user-identifier","saml","oidc","configuration"],"backgroundTag":null,"analyzedSha":"e93b962371d80964556f5590c6615f5160a437a1","analyzedAt":"2026-08-13T14:22:19.382Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}