bitwarden/server · error · NotFoundException

User not found.

Error message

User not found.

What it means

Thrown as a NotFoundException (HTTP 404) by the SCIM v2 UsersController Get action when the organization user details for {id} cannot be found or the retrieved user's OrganizationId does not match the route's {organizationId}. The SCIM exception filter returns a SCIM-formatted 404. This enforces tenant isolation and informs the IdP that the user resource no longer exists.

Source

Thrown at bitwarden_license/src/Scim/Controllers/v2/UsersController.cs:57

        IRestoreOrganizationUserCommand restoreOrganizationUserCommand,
        IRevokeOrganizationUserCommandV2 revokeOrganizationUserCommandV2)
    {
        _organizationUserRepository = organizationUserRepository;
        _getUsersListQuery = getUsersListQuery;
        _removeOrganizationUserCommand = removeOrganizationUserCommand;
        _patchUserCommand = patchUserCommand;
        _postUserCommand = postUserCommand;
        _restoreOrganizationUserCommand = restoreOrganizationUserCommand;
        _revokeOrganizationUserCommandV2 = revokeOrganizationUserCommandV2;
    }

    [HttpGet("{id}")]
    public async Task<IActionResult> Get(Guid organizationId, Guid id)
    {
        var orgUser = await _organizationUserRepository.GetDetailsByIdAsync(id);
        if (orgUser == null || orgUser.OrganizationId != organizationId)
        {
            throw new NotFoundException("User not found.");
        }
        return Ok(new ScimUserResponseModel(orgUser));
    }

    [HttpGet("")]
    public async Task<IActionResult> Get(
        Guid organizationId,
        [FromQuery] GetUsersQueryParamModel model)
    {
        var usersListQueryResult = await _getUsersListQuery.GetUsersListAsync(organizationId, model);
        var scimListResponseModel = new ScimListResponseModel<ScimUserResponseModel>
        {
            Resources = usersListQueryResult.userList.Select(u => new ScimUserResponseModel(u)).ToList(),
            ItemsPerPage = model.Count,
            TotalResults = usersListQueryResult.totalResults,
            StartIndex = model.StartIndex,
        };
        return Ok(scimListResponseModel);

View on GitHub (pinned to e93b962371)

Solutions

  1. List users via GET /v2/{organizationId}/Users to find the correct GUID for the user.
  2. If the user was removed, re-provision them via POST /v2/{organizationId}/Users.
  3. Verify the organizationId matches the SCIM bearer token's organization scope.
  4. Clear the IdP connector cache and re-sync to reconcile stale references.
Defensive patterns

Strategy: validation

Validate before calling

// Before GETting a specific user, verify the id is known
if (!await userExistsInIdpCache(userId))
{
    logger.Warn("User {UserId} not in local cache; skipping GET", userId);
    return null;
}

Try / catch

try { var user = await scimClient.GetUserAsync(orgId, userId); }
catch (ScimException ex) when (ex.StatusCode == 404)
{ /* user removed — delete from IdP tracking or re-provision */ }

Prevention

When it happens

Trigger: GET /v2/{organizationId}/Users/{id} where the organization user record was deleted, the user was revoked and purged, or the id belongs to a different organization. Occurs when an IdP queries for a user it cached but that has since been removed from Bitwarden.

Common situations: User was removed from the Bitwarden organization (or left the org) but the IdP still tracks them. Directory sync sent a GET for a user GUID that was never provisioned. Cross-tenant id confusion from misconfigured SCIM API keys.

Related errors


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