Skip to main content
The SCIM (System for Cross-domain Identity Management) service enables automated user and group provisioning for enterprise organizations using identity providers like Azure AD, Okta, and OneLogin.

Overview

SCIM is an enterprise feature available in the commercial version of Bitwarden Server.
The SCIM service provides:
  • SCIM 2.0 Protocol: Standards-compliant user and group provisioning
  • User Provisioning: Automatic user creation, updates, and deactivation
  • Group Management: Synchronize organizational groups and memberships
  • API Key Authentication: Secure authentication for identity providers
  • Real-time Sync: Instant propagation of identity changes

Architecture

SCIM 2.0 Specification

The service implements SCIM 2.0 core schema (RFC 7643) and protocol (RFC 7644):
  • Users: /v2/{orgId}/users
  • Groups: /v2/{orgId}/groups
  • Service Provider Config: /v2/ServiceProviderConfig
  • Resource Types: /v2/ResourceTypes
  • Schemas: /v2/Schemas

Configuration

From bitwarden_license/src/Scim/Startup.cs:29:
Service Configuration
public void ConfigureServices(IServiceCollection services)
{
    // Settings
    var globalSettings = services.AddGlobalSettingsServices(Configuration, Environment);
    services.Configure<ScimSettings>(Configuration.GetSection("ScimSettings"));
    
    // Data Protection
    services.AddCustomDataProtectionServices(Environment, globalSettings);
    
    // Stripe Billing
    StripeConfiguration.ApiKey = globalSettings.Stripe.ApiKey;
    
    // Repositories
    services.AddDatabaseRepositories(globalSettings);
    
    // Context
    services.AddScoped<ICurrentContext, CurrentContext>();
    services.AddScoped<IScimContext, ScimContext>();
    
    // API Key Authentication
    services.AddAuthentication(ApiKeyAuthenticationOptions.DefaultScheme)
        .AddScheme<ApiKeyAuthenticationOptions, ApiKeyAuthenticationHandler>(
            ApiKeyAuthenticationOptions.DefaultScheme, null);
    
    services.AddAuthorization(config =>
    {
        config.AddPolicy("Scim", policy =>
        {
            policy.RequireAuthenticatedUser();
            policy.RequireClaim(JwtClaimTypes.Scope, "api.scim");
        });
    });
    
    // SCIM Commands and Queries
    services.AddScimGroupCommands();
    services.AddScimGroupQueries();
    services.AddScimUserQueries();
    services.AddScimUserCommands();
}

Authentication

API Key Authentication

The SCIM service uses API key authentication instead of OAuth:
curl -X GET "https://scim.bitwarden.com/v2/{orgId}/users" \
  -H "Authorization: Bearer {scim_api_key}"
API Key Format: Organization-specific key generated in web vault Location: Organization Settings → SCIM Provisioning

SCIM Context

From bitwarden_license/src/Scim/Startup.cs:50:
SCIM Context
services.AddScoped<IScimContext, ScimContext>();
The SCIM context middleware validates:
  • API key authenticity
  • Organization ID matches authenticated organization
  • Organization has SCIM enabled
  • API key has not been revoked

Users Endpoint

List Users

GET /v2/{orgId}/users
GET /v2/{orgId}/users?filter=userName eq "[email protected]"
GET /v2/{orgId}/users?startIndex=1&count=100
Response:
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
  "totalResults": 150,
  "itemsPerPage": 100,
  "startIndex": 1,
  "Resources": [
    {
      "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
      "id": "guid",
      "externalId": "external_id",
      "userName": "[email protected]",
      "name": {
        "givenName": "First",
        "familyName": "Last"
      },
      "emails": [
        {
          "primary": true,
          "value": "[email protected]",
          "type": "work"
        }
      ],
      "active": true
    }
  ]
}

Create User

POST /v2/{orgId}/users
Content-Type: application/scim+json
Request Body
{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
  "userName": "[email protected]",
  "externalId": "external_id",
  "name": {
    "givenName": "First",
    "familyName": "Last"
  },
  "emails": [
    {
      "primary": true,
      "value": "[email protected]",
      "type": "work"
    }
  ],
  "active": true
}

Update User

PUT /v2/{orgId}/users/{id}
Content-Type: application/scim+json

Patch User

PATCH /v2/{orgId}/users/{id}
Content-Type: application/scim+json
Patch Operations
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [
    {
      "op": "replace",
      "path": "active",
      "value": false
    }
  ]
}

Delete User

DELETE /v2/{orgId}/users/{id}
Deleting a user via SCIM removes them from the organization but does not delete their Bitwarden account.

Groups Endpoint

From bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs:17:
Groups Controller
[Authorize("Scim")]
[Route("v2/{organizationId}/groups")]
[Produces("application/scim+json")]
public class GroupsController : Controller
{
    [HttpGet("{id}")]
    public async Task<IActionResult> Get(Guid organizationId, Guid id)
    {
        var group = await _groupRepository.GetByIdAsync(id);
        if (group == null || group.OrganizationId != organizationId)
        {
            throw new NotFoundException("Group not found.");
        }
        return Ok(new ScimGroupResponseModel(group));
    }
    
    [HttpPost("")]
    public async Task<IActionResult> Post(Guid organizationId, 
        [FromBody] ScimGroupRequestModel model)
    {
        var organization = await _organizationRepository.GetByIdAsync(organizationId);
        var group = await _postGroupCommand.PostGroupAsync(organization, model);
        return new CreatedResult(Url.Action(nameof(Get), 
            new { group.OrganizationId, group.Id }), 
            new ScimGroupResponseModel(group));
    }
    
    [HttpPatch("{id}")]
    public async Task<IActionResult> Patch(Guid organizationId, Guid id, 
        [FromBody] ScimPatchModel model)
    {
        var group = await _groupRepository.GetByIdAsync(id);
        if (group == null || group.OrganizationId != organizationId)
        {
            throw new NotFoundException("Group not found.");
        }
        
        await _patchGroupCommand.PatchGroupAsync(group, model);
        return new NoContentResult();
    }
}

List Groups

GET /v2/{orgId}/groups
GET /v2/{orgId}/groups?filter=displayName eq "Developers"
Response:
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"],
  "totalResults": 10,
  "itemsPerPage": 10,
  "startIndex": 1,
  "Resources": [
    {
      "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
      "id": "guid",
      "externalId": "external_id",
      "displayName": "Developers",
      "members": [
        {
          "value": "user_guid",
          "display": "[email protected]"
        }
      ]
    }
  ]
}

Create Group

POST /v2/{orgId}/groups
Content-Type: application/scim+json
{
  "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"],
  "displayName": "Developers",
  "externalId": "external_group_id",
  "members": [
    {
      "value": "user_guid"
    }
  ]
}

Update Group Membership

PATCH /v2/{orgId}/groups/{id}
Content-Type: application/scim+json
Add Members
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [
    {
      "op": "add",
      "path": "members",
      "value": [
        {"value": "user_guid_1"},
        {"value": "user_guid_2"}
      ]
    }
  ]
}
Remove Members
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
  "Operations": [
    {
      "op": "remove",
      "path": "members",
      "value": [
        {"value": "user_guid"}
      ]
    }
  ]
}

SCIM Commands and Queries

From bitwarden_license/src/Scim/Startup.cs:89:
SCIM Services
// Group operations
services.AddScimGroupCommands();
services.AddScimGroupQueries();

// User operations
services.AddScimUserQueries();
services.AddScimUserCommands();
These services implement:
  • Commands: Create, update, patch, delete operations
  • Queries: List, filter, search operations
  • Validation: SCIM schema compliance
  • Business Logic: Bitwarden-specific rules and constraints

Event Logging

All SCIM operations are logged with EventSystemUser.SCIM: From bitwarden_license/src/Scim/Controllers/v2/GroupsController.cs:112:
Event Logging
[HttpDelete("{id}")]
public async Task<IActionResult> Delete(Guid organizationId, Guid id)
{
    await _deleteGroupCommand.DeleteGroupAsync(organizationId, id, EventSystemUser.SCIM);
    return new NoContentResult();
}
Logged events:
  • User invited/confirmed/removed
  • Group created/updated/deleted
  • Group membership changes
  • Failed authentication attempts

Middleware Pipeline

From bitwarden_license/src/Scim/Startup.cs:95:
Request Pipeline
public void Configure(IApplicationBuilder app)
{
    // Security headers
    app.UseMiddleware<SecurityHeadersMiddleware>();
    
    // Forwarded headers (self-hosted)
    if (globalSettings.SelfHosted)
    {
        app.UseForwardedHeaders(globalSettings);
    }
    
    // Default middleware
    app.UseDefaultMiddleware(env, globalSettings);
    
    // Routing
    app.UseRouting();
    
    // SCIM context (validates organization)
    app.UseMiddleware<ScimContextMiddleware>();
    
    // Authentication & Authorization
    app.UseAuthentication();
    app.UseAuthorization();
    
    // Current context
    app.UseMiddleware<CurrentContextMiddleware>();
    
    // Controllers
    app.UseEndpoints(endpoints => endpoints.MapDefaultControllerRoute());
}

Supported Identity Providers

Azure AD

Microsoft Entra ID (Azure Active Directory) provisioning

Okta

Okta Identity Cloud integration

OneLogin

OneLogin directory synchronization

JumpCloud

JumpCloud directory integration

Setup Guide

1

Enable SCIM

Navigate to Organization Settings → SCIM Provisioning
2

Generate API Key

Click “Enable SCIM” to generate organization-specific API key
3

Configure IdP

In your identity provider, add Bitwarden SCIM application:
  • Base URL: https://scim.bitwarden.com/v2/{organizationId}
  • API Key: Use generated key as bearer token
4

Test Connection

Use IdP’s test connection feature to verify configuration
5

Assign Users**

Assign users and groups in IdP to begin provisioning

Filtering and Pagination

The SCIM service supports standard SCIM query parameters:

Filtering

GET /v2/{orgId}/users?filter=userName eq "[email protected]"
GET /v2/{orgId}/groups?filter=displayName sw "Dev"
Supported operators:
  • eq - equals
  • ne - not equals
  • sw - starts with
  • ew - ends with
  • co - contains

Pagination

GET /v2/{orgId}/users?startIndex=1&count=100
Parameters:
  • startIndex: 1-based index (default: 1)
  • count: Number of results per page (default: 100)

Error Handling

SCIM errors follow RFC 7644 error response format:
Error Response
{
  "schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
  "status": "404",
  "detail": "User not found."
}
Common status codes:
  • 400 - Bad Request (invalid SCIM request)
  • 401 - Unauthorized (invalid API key)
  • 404 - Not Found (resource doesn’t exist)
  • 409 - Conflict (duplicate external ID)
  • 500 - Internal Server Error

Deployment

Environment Variables

GLOBALSETTINGS__SELFHOSTED=true
GLOBALSETTINGS__SQLSERVER__CONNECTIONSTRING=<connection>
SCIMSETTINGS__ENABLED=true

Docker

docker run -d \
  --name bitwarden-scim \
  -p 5002:5000 \
  -e GLOBALSETTINGS__SelfHosted=true \
  -e GLOBALSETTINGS__SqlServer__ConnectionString="<connection>" \
  bitwarden/scim:latest

Rate Limiting

SCIM endpoints may be rate-limited to prevent abuse. Implement exponential backoff in IdP sync jobs.
Recommended sync intervals:
  • Full sync: Every 24 hours
  • Incremental sync: Every 30-60 minutes
  • Real-time updates: As changes occur (with backoff)

Troubleshooting

Common Issues

IssueSolution
401 UnauthorizedVerify API key is correct and SCIM is enabled
404 Not FoundCheck organization ID in URL matches API key
409 ConflictExternal ID already exists, use unique identifiers
User not receiving invitationCheck email settings and spam filters

Debug Logging

{
  "Logging": {
    "LogLevel": {
      "Bit.Scim": "Debug"
    }
  }
}

Limitations

SCIM provisioning has the following limitations:
  • Users cannot be created without an email address
  • Only organization users are managed (not entire Bitwarden accounts)
  • Custom attributes are not supported
  • Password management is not supported via SCIM
  • Collections are not managed via SCIM

Best Practices

  1. External IDs: Always set unique externalId for users and groups
  2. Incremental Sync: Use incremental sync to reduce API calls
  3. Error Handling: Implement proper error handling and retry logic
  4. Monitoring: Monitor SCIM sync logs in IdP admin console
  5. Testing: Test with small group before full deployment

Build docs developers (and LLMs) love