Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

SCIM Server Guide

Welcome to the comprehensive guide for the SCIM Server library! This guide will help you understand and use the Rust components for building enterprise-ready identity provisioning systems.

The Problem

Your application needs to support enterprise customers, but they require SCIM provisioning—the ability to automatically create, update, and delete user accounts from their identity systems (Okta, Azure Entra, Google Workspace, etc.).

Research shows that authentication requirements become critical blockers in 75-80% of enterprise deals, with companies losing an average of 3-5 enterprise deals annually due to insufficient identity capabilities. Building SCIM compliance seems straightforward at first: it's just REST APIs with JSON. But enterprise identity management has many hidden complexities that create months of unexpected work:

  • Provider Fragmentation: Identity providers interpret SCIM differently—email handling, user deactivation, and custom attributes work differently across Okta, Azure, and Google
  • Protocol Compliance: SCIM 2.0 has strict requirements with 10 common implementation pitfalls that cause enterprise integration failures
  • Hidden Development Costs: Industry data shows 3-6 months and $3.5M+ in development costs for homegrown SSO/SCIM solutions over 3 years
  • Ongoing Maintenance: Security incidents, provider-specific bugs, and manual customer onboarding create continuous overhead
  • Schema Complexity: Extensible schemas with custom attributes while maintaining interoperability across different enterprise environments

Many developers underestimate this complexity and spend months debugging provider-specific edge cases, dealing with "more deviation than standard" implementations, and handling enterprise customers who discover integration issues in production.

What is SCIM Server?

SCIM Server is a Rust library that provides all the essential components for building SCIM 2.0-compliant systems. Instead of implementing SCIM from scratch, you get proven building blocks that handle the complex parts while letting you focus on your application logic.

The library uses the SCIM 2.0 protocol as a framework to standardize identity data validation and processing. You compose the components you need—from simple single-tenant systems to complex multi-tenant platforms with custom schemas and AI integration.

What You Get

Ready-to-Use Components

  • StandardResourceProvider: Complete SCIM resource operations for typical use cases
  • InMemoryStorage: Development and testing storage backend
  • Schema Registry: Pre-loaded with RFC 7643 User and Group schemas
  • ETag Versioning: Automatic concurrency control for production deployments

Extension Points

  • ResourceProvider trait: Implement for custom business logic and data models
  • StorageProvider trait: Connect to any database or storage system
  • Custom Value Objects: Type-safe handling of domain-specific attributes
  • Multi-Tenant Context: Built-in tenant isolation and context management

Enterprise Features

  • Protocol Compliance: All the RFC 7643/7644 complexity handled correctly
  • Schema Extensions: Add custom attributes while maintaining SCIM compatibility
  • AI Integration: Model Context Protocol support for AI agent interactions
  • Production Ready: Structured logging, error handling, and performance optimizations

Time & Cost Savings

Instead of facing the typical 3-6 month development timeline and $3.5M+ costs that industry data shows for homegrown solutions, focus on your application:

Building From ScratchUsing SCIM Server
❌ 3-6 months learning SCIM protocol complexities✅ Start building immediately with working components
❌ $3.5M+ development and maintenance costs over 3 years✅ Fraction of the cost with proven components
❌ Debugging provider-specific implementation differences✅ Handle Okta, Azure, Google variations automatically
❌ Building multi-tenant isolation from scratch✅ Multi-tenant context and isolation built-in
❌ Lost enterprise deals due to auth requirements✅ Enterprise-ready identity provisioning components

Result: Avoid the 75-80% of enterprise deals that stall on authentication by having production-ready SCIM components instead of months of custom development.

Who Should Use This?

This library is designed for Rust developers who need to:

  • Add enterprise customer support to SaaS applications requiring SCIM provisioning
  • Build identity management tools that integrate with multiple identity providers
  • Create AI agents that need to manage user accounts and permissions
  • Develop custom identity solutions with specific business requirements
  • Integrate existing systems with enterprise identity infrastructure

How to Use This Guide

The guide is organized into progressive sections:

  1. Getting Started: Quick setup and basic usage
  2. Core Concepts: Understanding the fundamental ideas
  3. Tutorials: Step-by-step guides for common scenarios
  4. How-To Guides: Solutions for specific problems
  5. Advanced Topics: Deep dives into complex scenarios
  6. Reference: Technical specifications and details

Learning Path

New to SCIM? Start with SCIM Protocol Overview to understand the standard.

Ready to code? Jump to Your First SCIM Server for hands-on experience.

Building production systems? Read through Core Concepts and Advanced Topics.

Solving specific problems? Use the How-To Guides section.

What You'll Learn

By the end of this guide, you'll understand how to:

  • Compose SCIM Server components for your specific requirements
  • Implement the ResourceProvider trait for your application's data model
  • Create custom schema extensions and value objects
  • Build multi-tenant systems using the provided context components
  • Integrate SCIM components with web frameworks and AI tools
  • Deploy production systems using the concurrency and observability components

Getting Help

Let's get started! 🚀


References

Enterprise authentication challenges and statistics sourced from: Gupta, "Enterprise Authentication: The Hidden SaaS Growth Blocker", 2024; WorkOS "Build vs Buy" analysis, 2024; WorkOS ROI comparison, 2024.

SCIM implementation pitfalls from: Traxion "10 Most Common Pitfalls for SCIM 2.0 Compliant API Implementations" based on testing 40-50 SCIM implementations.

Provider-specific differences documented in: WorkOS "SCIM Challenges", 2024.

Installation

This guide will get you up and running with the SCIM server library in under 5 minutes.

Prerequisites

To verify your installation:

rustc --version
cargo --version

Adding the Dependency

Add to your Cargo.toml:

[dependencies]
scim-server = "=0.3.7"
tokio = { version = "1.0", features = ["full"] }
serde_json = "1.0"

Note: The library is under active development. Pin to exact versions for stability. Breaking changes are signaled by minor version increments until v1.0.

Verification

Create a simple test to verify the installation works:

use scim_server::{
    ScimServer, providers::StandardResourceProvider, storage::InMemoryStorage,
    RequestContext
};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let storage = InMemoryStorage::new();
    let provider = StandardResourceProvider::new(storage);
    let server = ScimServer::new(provider)?;
    let context = RequestContext::new("test".to_string());

    let user_data = json!({
        "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
        "userName": "john.doe",
        "active": true
    });

    let user = server.create_resource("User", user_data, &context).await?;
    let retrieved = server.get_resource("User", user.get_id().unwrap(), &context).await?;

    assert_eq!(retrieved.get_attribute("active").unwrap(), &json!(true));

    Ok(())
}

Run with:

cargo run

If this runs without errors, your installation is working correctly!

Next Steps

Once installation is complete, proceed to:

For production deployments, see the Production Setup Guide for information about system requirements, databases, and scaling considerations.

Your First SCIM Server

Learn to build a working SCIM server in 10 minutes using this library.

Quick Start

1. Create a New Project

cargo new my-scim-server
cd my-scim-server

2. Add Dependencies

[dependencies]
scim-server = "0.3.7"
tokio = { version = "1.0", features = ["full"] }
serde_json = "1.0"

3. Basic Server (15 lines)

use scim_server::{
    providers::StandardResourceProvider,
    storage::InMemoryStorage,
    RequestContext,
};
use serde_json::json;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // Create storage and provider - the foundation of your SCIM server
    let storage = InMemoryStorage::new();  // Simple storage for development
    let provider = StandardResourceProvider::new(storage);  // Main SCIM interface

    // Create a single-tenant request context - tracks this operation for logging
    let context = RequestContext::new("demo".to_string());
    let user_data = json!({
        "userName": "john.doe",
        "emails": [{"value": "john@example.com", "primary": true}],
        "active": true
    });

    let user = provider.create_resource("User", user_data, &context).await?;
    println!("Created user: {}", user.get_username().unwrap());

    Ok(())
}

4. Run It

cargo run
# Output: Created user: john.doe

Core Operations

Setup

For the following examples, we'll use this provider and context setup:

#![allow(unused)]
fn main() {
use scim_server::{
    providers::StandardResourceProvider,
    storage::InMemoryStorage,
    RequestContext,
};
use serde_json::json;

// Create storage and provider - the foundation of your SCIM server
let storage = InMemoryStorage::new();  // Simple storage for development
let provider = StandardResourceProvider::new(storage);  // Main SCIM interface

// Single-tenant RequestContext tracks each operation for logging
let context = RequestContext::new("demo".to_string());
}

All the following examples will use these provider and context variables.

Creating Resources

#![allow(unused)]
fn main() {
// Use JSON to define user attributes following SCIM 2.0 schema
let user_data = json!({
    "userName": "alice.smith",
    "name": {
        "givenName": "Alice",
        "familyName": "Smith"
    },
    "emails": [{"value": "alice@company.com", "primary": true}],
    "active": true
});

// Create the user - provider handles validation and storage
let user = provider.create_resource("User", user_data, &context).await?;
let user_id = user.get_id().unwrap();  // Get the auto-generated unique ID
}

Reading Resources

#![allow(unused)]
fn main() {
// Get user by ID - returns Option<Resource> (None if not found)
let retrieved_user = provider.get_resource("User", &user_id, &context).await?;

if let Some(user) = retrieved_user {
    println!("Found: {}", user.get_username().unwrap());
}

// Search by specific attribute value - useful for username lookups
let found_user = provider
    .find_resource_by_attribute("User", "userName", &json!("alice.smith"), &context)
    .await?;
}

Updating Resources

#![allow(unused)]
fn main() {
// Updates require the full resource data, including the ID
let update_data = json!({
    "id": user_id,
    "userName": "alice.smith",
    "name": {
        "givenName": "Alice",
        "familyName": "Johnson"  // Changed surname
    },
    "emails": [{"value": "alice@company.com", "primary": true}],
    "active": false  // Deactivated
});

// Update replaces the entire resource with new data
let updated_user = provider
    .update_resource("User", &user_id, update_data, &context)
    .await?;
}

Listing and Searching

#![allow(unused)]
fn main() {
// List all users - None means no pagination/filtering
let all_users = provider.list_resources("User", None, &context).await?;
println!("Total users: {}", all_users.len());

// Efficiently check existence without retrieving full data
let exists = provider.resource_exists("User", &user_id, &context).await?;
println!("User exists: {}", exists);
}

Validation and Error Handling

#![allow(unused)]
fn main() {
// The provider automatically validates data against SCIM schemas
let invalid_user = json!({
    "userName": "",  // Empty username - violates SCIM requirements
    "emails": [{"value": "not-an-email"}],  // Invalid email format
});

// Always handle validation errors gracefully
match provider.create_resource("User", invalid_user, &context).await {
    Ok(user) => println!("User created: {}", user.get_id().unwrap()),
    Err(e) => println!("Validation failed: {}", e),  // Detailed error message
}
}

Deleting Resources

#![allow(unused)]
fn main() {
// Delete a resource by ID
provider.delete_resource("User", &user_id, &context).await?;

// Verify deletion
let exists = provider.resource_exists("User", &user_id, &context).await?;
println!("User still exists: {}", exists); // Should be false
}

Working with Groups

#![allow(unused)]
fn main() {
// Groups can contain users as members - useful for access control
// Create a group (assuming you have a user_id from previous examples)
let group_data = json!({
    "displayName": "Engineering Team",  // Required: human-readable name
    "members": [  // Optional: list of member references
        {
            "value": user_id,  // Reference to the user's ID
            "$ref": format!("https://example.com/v2/Users/{}", user_id),  // Full URI
            "type": "User"  // Type of the referenced resource
        }
    ]
});

// Using the context and user_id from previous examples
// Create group just like users - same provider interface
let group = provider.create_resource("Group", group_data, &context).await?;
println!("Created group: {}", group.get_attribute("displayName").unwrap());
}

Multi-Tenant Support

For multi-tenant scenarios, you create explicit tenant contexts instead of using the default single-tenant setup:

#![allow(unused)]
fn main() {
// Import TenantContext for multi-tenant operations
use scim_server::resource::TenantContext;

// Create the same provider as before
let storage = InMemoryStorage::new();
let provider = StandardResourceProvider::new(storage);

// Multi-tenant contexts - each gets isolated data space
let tenant_a = TenantContext::new("company-a".to_string(), "client-123".to_string());
let tenant_a_context = RequestContext::with_tenant("req-a".to_string(), tenant_a);

let tenant_b = TenantContext::new("company-b".to_string(), "client-456".to_string());
let tenant_b_context = RequestContext::with_tenant("req-b".to_string(), tenant_b);

// Same provider, different tenants - data is completely isolated
provider.create_resource("User", user_data.clone(), &tenant_a_context).await?;
provider.create_resource("User", user_data, &tenant_b_context).await?;

// Each tenant sees only their own data
let tenant_a_users = provider.list_resources("User", None, &tenant_a_context).await?;
let tenant_b_users = provider.list_resources("User", None, &tenant_b_context).await?;

println!("Company A users: {}", tenant_a_users.len());
println!("Company B users: {}", tenant_b_users.len());
}

Provider Statistics

#![allow(unused)]
fn main() {
// Useful for monitoring and debugging your SCIM server
let stats = provider.get_stats().await;
println!("Total tenants: {}", stats.tenant_count);  // Number of active tenants
println!("Total resources: {}", stats.total_resources);  // Users + Groups + etc.
println!("Resource types: {:?}", stats.resource_types);  // ["User", "Group", ...]
}

Next Steps

Complete Examples

See the examples directory for full working implementations:

Running Examples

# Run any example to see it in action
cargo run --example basic_usage
cargo run --example group_example

Key Concepts

  • StandardResourceProvider - Main interface for SCIM operations
  • InMemoryStorage - Simple storage backend for development
  • RequestContext - Request tracking and tenant isolation
  • Resource Types - "User", "Group", or custom types
  • JSON Data - All resource data uses serde_json::Value

You now have a working SCIM server! The examples above demonstrate all core functionality needed for most SCIM implementations.

Architecture

The SCIM Server follows a clean trait-based architecture with clear separation of concerns designed for maximum composability and extensibility.

Component Architecture

The library is built around composable traits that you implement for your specific needs:

┌─────────────────┐    ┌──────────────────┐    ┌─────────────────┐
│  Client Layer   │    │   SCIM Server    │    │ Resource Layer  │
│                 │    │                  │    │                 │
│  • MCP AI       │───▶│  • Operations    │───▶│ ResourceProvider│
│  • Web Framework│    │  • Multi-tenant  │    │      trait      │
│  • Custom       │    │  • Type Safety   │    │                 │
└─────────────────┘    └──────────────────┘    └─────────────────┘
                              │                          │
                              ▼                          ▼
                       ┌──────────────────┐    ┌─────────────────┐
                       │ Schema System    │    │ Storage Layer   │
                       │                  │    │                 │
                       │ • SchemaRegistry │    │ StorageProvider │
                       │ • Validation     │    │      trait      │
                       │ • Value Objects  │    │  • In-Memory    │
                       │ • Extensions     │    │  • Database     │
                       └──────────────────┘    │  • Custom       │
                                               └─────────────────┘

Layer Responsibilities

Client Layer: Your integration points - compose these components into web endpoints, AI tools, or custom applications.

SCIM Server: Orchestration component that coordinates resource operations using your provider implementations.

Resource Layer: ResourceProvider trait - implement this interface for your data model, or use the provided StandardResourceProvider for common scenarios.

Schema System: Schema registry and validation components - extend with custom schemas and value objects.

Storage Layer: StorageProvider trait - use the provided InMemoryStorage for development, or connect to any database or custom backend.

Core Traits

ResourceProvider

Your main integration point for SCIM resource operations:

#![allow(unused)]
fn main() {
pub trait ResourceProvider {
    type Error: std::error::Error + Send + Sync + 'static;

    async fn create_resource(
        &self,
        resource_type: &str,
        data: Value,
        context: &RequestContext,
    ) -> Result<Resource, Self::Error>;

    async fn get_resource(
        &self,
        resource_type: &str,
        id: &str,
        context: &RequestContext,
    ) -> Result<Option<Resource>, Self::Error>;

    // ... other CRUD operations
}
}

Implementation Options:

  • Use StandardResourceProvider<S> with any StorageProvider for typical use cases
  • Implement directly for custom business logic and data models
  • Wrap existing services or databases

StorageProvider

Pure data persistence abstraction:

#![allow(unused)]
fn main() {
pub trait StorageProvider: Send + Sync {
    type Error: std::error::Error + Send + Sync + 'static;

    async fn put(&self, key: StorageKey, data: Value) -> Result<Value, Self::Error>;
    async fn get(&self, key: StorageKey) -> Result<Option<Value>, Self::Error>;
    async fn delete(&self, key: StorageKey) -> Result<bool, Self::Error>;
    async fn list(&self, prefix: StoragePrefix) -> Result<Vec<Value>, Self::Error>;
}
}

Implementation Options:

  • Use InMemoryStorage for development and testing
  • Implement for your database (PostgreSQL, MongoDB, etc.)
  • Connect to cloud storage or external APIs

Value Objects

Type-safe SCIM attribute handling:

#![allow(unused)]
fn main() {
pub trait ValueObject: Debug + Send + Sync {
    fn attribute_type(&self) -> AttributeType;
    fn to_json(&self) -> ValidationResult<Value>;
    fn validate_against_schema(&self, definition: &AttributeDefinition) -> ValidationResult<()>;
    // ...
}

pub trait SchemaConstructible: ValueObject + Sized {
    fn from_schema_and_value(
        definition: &AttributeDefinition,
        value: &Value,
    ) -> ValidationResult<Self>;
    // ...
}
}

Extension Points:

  • Create custom value objects for domain-specific attributes
  • Implement validation logic for business rules
  • Support for complex multi-valued attributes

Multi-Tenant Architecture

The library provides several components for multi-tenant systems:

TenantResolver

Maps authentication credentials to tenant context:

#![allow(unused)]
fn main() {
pub trait TenantResolver: Send + Sync {
    type Error: std::error::Error + Send + Sync + 'static;

    async fn resolve_tenant(&self, credential: &str) -> Result<TenantContext, Self::Error>;
}
}

RequestContext

Carries tenant and request information through all operations:

#![allow(unused)]
fn main() {
pub struct RequestContext {
    pub request_id: String,
    tenant_context: Option<TenantContext>,
}
}

Tenant Isolation

  • All ResourceProvider operations include RequestContext
  • Storage keys automatically include tenant ID
  • Schema validation respects tenant-specific extensions

Schema System Architecture

SchemaRegistry

Central registry for SCIM schemas:

  • Loads and validates RFC 7643 core schemas
  • Supports custom schema extensions
  • Provides validation services for all operations

Dynamic Value Objects

  • Runtime creation from schema definitions
  • Type-safe attribute handling
  • Extensible factory pattern for custom types

Extension Model

  • Custom resource types beyond User/Group
  • Organization-specific attributes
  • Maintains SCIM compliance and interoperability

Concurrency Control

ETag-Based Versioning

Built into the core architecture:

  • Automatic version generation from resource content
  • Conditional operations (If-Match, If-None-Match)
  • Conflict detection and resolution
  • Production-ready optimistic locking

Version-Aware Operations

All resource operations support conditional execution:

#![allow(unused)]
fn main() {
// Conditional update with version check
let result = provider.conditional_update(
    "User", 
    &user_id, 
    updated_data, 
    &expected_version, 
    &context
).await?;

match result {
    ConditionalResult::Success(resource) => // Update succeeded
    ConditionalResult::PreconditionFailed => // Version conflict
}
}

Integration Patterns

Web Framework Integration

Components work with any HTTP framework:

  1. Extract SCIM request details
  2. Create RequestContext with tenant info
  3. Call appropriate ScimServer operations
  4. Format responses per SCIM specification

AI Agent Integration

Model Context Protocol (MCP) components:

  1. Expose SCIM operations as discoverable tools
  2. Structured schemas for AI understanding
  3. Error handling designed for AI decision making
  4. Multi-tenant aware tool descriptions

Custom Client Integration

Direct component usage:

  1. Implement ResourceProvider for your data model
  2. Choose appropriate StorageProvider
  3. Configure schema extensions as needed
  4. Build custom API layer or integration logic

Performance Considerations

Async-First Design

  • All I/O operations are async
  • Non-blocking concurrent operations
  • Efficient resource utilization

Minimal Allocations

  • Zero-copy JSON processing where possible
  • Efficient value object system
  • Smart caching in schema registry

Scalability Features

  • Pluggable storage for horizontal scaling
  • Multi-tenant isolation for SaaS platforms
  • Connection pooling support through storage traits