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 casesInMemoryStorage
: 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 modelsStorageProvider
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 Scratch | Using 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:
- Getting Started: Quick setup and basic usage
- Core Concepts: Understanding the fundamental ideas
- Tutorials: Step-by-step guides for common scenarios
- How-To Guides: Solutions for specific problems
- Advanced Topics: Deep dives into complex scenarios
- 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
- Examples: Check the examples directory for working code
- API Documentation: See docs.rs for detailed API reference
- Issues: Report bugs or ask questions on GitHub Issues
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
- Rust 1.75 or later - Install Rust
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:
- Your First SCIM Server - Build a complete working implementation
- Configuration Guide - Learn about storage backends and advanced setup
- API Reference - Explore all available operations
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
- HTTP Server Integration - Add REST endpoints with Axum or Actix
- Multi-tenant Setup - Advanced tenant isolation and management
- Advanced Features - Groups, custom schemas, bulk operations
- Storage Backends - PostgreSQL, SQLite, and custom storage
Complete Examples
See the examples directory for full working implementations:
- basic_usage.rs - Complete CRUD operations
- group_example.rs - Group management with members
- multi_tenant_example.rs - Tenant isolation patterns
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 operationsInMemoryStorage
- Simple storage backend for developmentRequestContext
- 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 anyStorageProvider
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 includeRequestContext
- 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:
- Extract SCIM request details
- Create
RequestContext
with tenant info - Call appropriate
ScimServer
operations - Format responses per SCIM specification
AI Agent Integration
Model Context Protocol (MCP) components:
- Expose SCIM operations as discoverable tools
- Structured schemas for AI understanding
- Error handling designed for AI decision making
- Multi-tenant aware tool descriptions
Custom Client Integration
Direct component usage:
- Implement
ResourceProvider
for your data model - Choose appropriate
StorageProvider
- Configure schema extensions as needed
- 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