Try Live
Add Docs
Rankings
Pricing
Enterprise
Docs
Install
Theme
Install
Docs
Pricing
Enterprise
More...
More...
Try Live
Rankings
Create API Key
Add Docs
Django SCIM 2.0
https://github.com/15five/django-scim2
Admin
A SCIM 2.0 Service Provider Implementation (for Django)
Tokens:
10,422
Snippets:
58
Trust Score:
5.8
Update:
3 weeks ago
Context
Skills
Chat
Benchmark
89.8
Suggestions
Latest
Show doc for...
Code
Info
Show Results
Context Summary (auto-generated)
Raw
Copy
Link
# django-scim2 django-scim2 is a provider-side implementation of the SCIM 2.0 (System for Cross-domain Identity Management) specification for Django applications. It enables automated user provisioning and deprovisioning between identity providers (like Okta, Azure AD, OneLogin) and your Django application, allowing enterprise customers to manage user accounts through their existing identity management systems. The library provides a complete REST API for managing Users and Groups according to the SCIM 2.0 RFC specifications. It includes customizable adapters that map SCIM schema attributes to your Django models, filter query parsing for searching resources, and configurable authentication middleware. The architecture is highly extensible, allowing developers to override default behaviors for user/group management, implement custom filtering logic, and integrate with any authentication system. ## Installation and Basic Setup Install the package and configure Django settings with required SCIM service provider options. ```python # Install via pip # pip install django-scim2 # settings.py INSTALLED_APPS = [ 'django.contrib.auth', 'django.contrib.contenttypes', # ... other apps 'django_scim', ] # URL configuration - root urls.py from django.urls import path, include urlpatterns = [ # ... other urls path('scim/v2/', include('django_scim.urls')), ] # Required SCIM settings in settings.py SCIM_SERVICE_PROVIDER = { 'NETLOC': 'api.example.com', 'SCHEME': 'https', 'AUTHENTICATION_SCHEMES': [ { 'type': 'oauth2', 'name': 'OAuth 2', 'description': 'OAuth 2 implemented with bearer token', 'specUri': 'https://tools.ietf.org/html/rfc6750', 'documentationUri': 'https://example.com/docs/scim', }, ], } ``` ## GET /Users - List and Filter Users Retrieve all users or filter users using SCIM filter syntax with pagination support. ```bash # List all users with pagination curl -X GET "https://api.example.com/scim/v2/Users?startIndex=1&count=10" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/scim+json" # Filter users by username curl -X GET "https://api.example.com/scim/v2/Users?filter=userName%20eq%20%22john.doe%22" \ -H "Authorization: Bearer <token>" # Filter users by active status curl -X GET "https://api.example.com/scim/v2/Users?filter=active%20eq%20true" \ -H "Authorization: Bearer <token>" # Response example: { "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], "totalResults": 100, "itemsPerPage": 10, "startIndex": 1, "Resources": [ { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "2819c223-7f76-453a-919d-413861904646", "externalId": "701984", "userName": "john.doe@example.com", "name": { "givenName": "John", "familyName": "Doe", "formatted": "John Doe" }, "displayName": "John Doe", "emails": [{"value": "john.doe@example.com", "primary": true}], "active": true, "groups": [ { "value": "e9e30dba-f08f-4109-8486-d5c6a331660a", "$ref": "https://api.example.com/scim/v2/Groups/e9e30dba-f08f-4109-8486-d5c6a331660a", "display": "Engineering" } ], "meta": { "resourceType": "User", "created": "2024-01-15T09:30:00Z", "lastModified": "2024-01-20T14:22:00Z", "location": "https://api.example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646" } } ] } ``` ## GET /Users/{id} - Get Single User Retrieve a specific user by their SCIM ID. ```bash curl -X GET "https://api.example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/scim+json" # Response: { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "2819c223-7f76-453a-919d-413861904646", "externalId": "701984", "userName": "john.doe@example.com", "name": { "givenName": "John", "familyName": "Doe", "formatted": "John Doe" }, "displayName": "John Doe", "emails": [{"value": "john.doe@example.com", "primary": true}], "active": true, "groups": [], "meta": { "resourceType": "User", "created": "2024-01-15T09:30:00Z", "lastModified": "2024-01-20T14:22:00Z", "location": "https://api.example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646" } } ``` ## POST /Users - Create User Create a new user with the provided SCIM attributes. ```bash curl -X POST "https://api.example.com/scim/v2/Users" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "jane.smith@example.com", "externalId": "702001", "name": { "givenName": "Jane", "familyName": "Smith" }, "emails": [ {"value": "jane.smith@example.com", "primary": true}, {"value": "jane.s@example.com", "primary": false} ], "active": true, "password": "initial-password-123" }' # Response (201 Created): { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "550e8400-e29b-41d4-a716-446655440000", "externalId": "702001", "userName": "jane.smith@example.com", "name": { "givenName": "Jane", "familyName": "Smith", "formatted": "Jane Smith" }, "displayName": "Jane Smith", "emails": [{"value": "jane.smith@example.com", "primary": true}], "active": true, "groups": [], "meta": { "resourceType": "User", "created": "2024-01-21T10:00:00Z", "lastModified": "2024-01-21T10:00:00Z", "location": "https://api.example.com/scim/v2/Users/550e8400-e29b-41d4-a716-446655440000" } } ``` ## PUT /Users/{id} - Replace User Fully replace a user resource with the provided attributes. ```bash curl -X PUT "https://api.example.com/scim/v2/Users/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "userName": "jane.smith@example.com", "externalId": "702001", "name": { "givenName": "Jane", "familyName": "Smith-Johnson" }, "emails": [{"value": "jane.smith@example.com", "primary": true}], "active": true }' # Response (200 OK): { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"], "id": "550e8400-e29b-41d4-a716-446655440000", "userName": "jane.smith@example.com", "name": { "givenName": "Jane", "familyName": "Smith-Johnson", "formatted": "Jane Smith-Johnson" }, "displayName": "Jane Smith-Johnson", "active": true, "meta": { "resourceType": "User", "lastModified": "2024-01-22T08:30:00Z", "location": "https://api.example.com/scim/v2/Users/550e8400-e29b-41d4-a716-446655440000" } } ``` ## PATCH /Users/{id} - Partial Update User Update specific user attributes using SCIM PATCH operations (add, remove, replace). ```bash # Deactivate a user curl -X PATCH "https://api.example.com/scim/v2/Users/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ {"op": "replace", "path": "active", "value": false} ] }' # Update multiple attributes curl -X PATCH "https://api.example.com/scim/v2/Users/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ {"op": "replace", "value": {"givenName": "Janet", "familyName": "Smith"}}, {"op": "replace", "path": "emails", "value": [{"value": "janet.smith@example.com", "primary": true}]} ] }' # Response (200 OK): Returns the updated user resource ``` ## DELETE /Users/{id} - Delete User Remove a user from the system. ```bash curl -X DELETE "https://api.example.com/scim/v2/Users/550e8400-e29b-41d4-a716-446655440000" \ -H "Authorization: Bearer <token>" # Response: 204 No Content ``` ## GET /Groups - List and Filter Groups Retrieve all groups or filter groups using SCIM filter syntax. ```bash # List all groups curl -X GET "https://api.example.com/scim/v2/Groups?startIndex=1&count=20" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/scim+json" # Response: { "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], "totalResults": 5, "itemsPerPage": 20, "startIndex": 1, "Resources": [ { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "id": "e9e30dba-f08f-4109-8486-d5c6a331660a", "externalId": "engineering-team", "displayName": "Engineering", "members": [ { "value": "2819c223-7f76-453a-919d-413861904646", "$ref": "https://api.example.com/scim/v2/Users/2819c223-7f76-453a-919d-413861904646", "display": "John Doe" } ], "meta": { "resourceType": "Group", "location": "https://api.example.com/scim/v2/Groups/e9e30dba-f08f-4109-8486-d5c6a331660a" } } ] } ``` ## POST /Groups - Create Group Create a new group resource. ```bash curl -X POST "https://api.example.com/scim/v2/Groups" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "displayName": "Marketing", "externalId": "marketing-team" }' # Response (201 Created): { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:Group"], "id": "a1b2c3d4-e5f6-7890-abcd-ef1234567890", "displayName": "Marketing", "externalId": "marketing-team", "members": [], "meta": { "resourceType": "Group", "location": "https://api.example.com/scim/v2/Groups/a1b2c3d4-e5f6-7890-abcd-ef1234567890" } } ``` ## PATCH /Groups/{id} - Add/Remove Group Members Modify group membership using PATCH operations. ```bash # Add members to a group curl -X PATCH "https://api.example.com/scim/v2/Groups/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ { "op": "add", "path": "members", "value": [ {"value": "2819c223-7f76-453a-919d-413861904646"}, {"value": "550e8400-e29b-41d4-a716-446655440000"} ] } ] }' # Remove members from a group curl -X PATCH "https://api.example.com/scim/v2/Groups/a1b2c3d4-e5f6-7890-abcd-ef1234567890" \ -H "Authorization: Bearer <token>" \ -H "Content-Type: application/scim+json" \ -d '{ "schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"], "Operations": [ { "op": "remove", "path": "members", "value": [{"value": "550e8400-e29b-41d4-a716-446655440000"}] } ] }' # Response (200 OK): Returns updated group resource ``` ## GET /ServiceProviderConfig - Service Provider Configuration Retrieve the SCIM service provider's configuration and capabilities. ```bash curl -X GET "https://api.example.com/scim/v2/ServiceProviderConfig" \ -H "Authorization: Bearer <token>" # Response: { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:ServiceProviderConfig"], "documentationUri": null, "patch": {"supported": true}, "bulk": {"supported": false, "maxOperations": 1000, "maxPayloadSize": 1048576}, "filter": {"supported": false, "maxResults": 50}, "changePassword": {"supported": true}, "sort": {"supported": false}, "etag": {"supported": false}, "authenticationSchemes": [ { "type": "oauth2", "name": "OAuth 2", "description": "OAuth 2 implemented with bearer token" } ], "meta": { "location": "https://api.example.com/scim/v2/ServiceProviderConfig", "resourceType": "ServiceProviderConfig" } } ``` ## GET /ResourceTypes - Resource Types Discovery Retrieve metadata about supported resource types (Users, Groups). ```bash curl -X GET "https://api.example.com/scim/v2/ResourceTypes" \ -H "Authorization: Bearer <token>" # Response: { "schemas": ["urn:ietf:params:scim:api:messages:2.0:ListResponse"], "Resources": [ { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"], "id": "User", "name": "User", "endpoint": "/scim/v2/Users", "description": "User Account", "schema": "urn:ietf:params:scim:schemas:core:2.0:User", "meta": { "location": "https://api.example.com/scim/v2/ResourceTypes/User", "resourceType": "ResourceType" } }, { "schemas": ["urn:ietf:params:scim:schemas:core:2.0:ResourceType"], "id": "Group", "name": "Group", "endpoint": "/scim/v2/Groups", "description": "Group", "schema": "urn:ietf:params:scim:schemas:core:2.0:Group", "meta": { "location": "https://api.example.com/scim/v2/ResourceTypes/Group", "resourceType": "ResourceType" } } ] } ``` ## GET /Schemas - Schema Discovery Retrieve SCIM schema definitions for User and Group resources. ```bash curl -X GET "https://api.example.com/scim/v2/Schemas" \ -H "Authorization: Bearer <token>" # Get specific schema curl -X GET "https://api.example.com/scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User" \ -H "Authorization: Bearer <token>" ``` ## Custom User Adapter Create a custom adapter to map SCIM attributes to your Django user model by extending SCIMUser. ```python # adapters.py from django_scim.adapters import SCIMUser from django_scim import exceptions as scim_exceptions from django_scim.utils import get_group_adapter class CustomSCIMUser(SCIMUser): """Custom adapter mapping SCIM schema to your User model.""" @property def display_name(self): """Return displayName per SCIM spec.""" if self.obj.first_name and self.obj.last_name: return f'{self.obj.first_name} {self.obj.last_name}' return self.obj.email @property def groups(self): """Return user's groups per SCIM spec.""" from myapp.models import Group group_qs = Group.objects.filter(members=self.obj) scim_groups = [get_group_adapter()(g, self.request) for g in group_qs] return [ { 'value': group.id, '$ref': group.location, 'display': group.display_name, } for group in scim_groups ] @property def meta(self): """Return meta object per SCIM spec.""" return { 'resourceType': self.resource_type, 'created': self.obj.date_joined.isoformat(), 'lastModified': self.obj.updated_at.isoformat(), 'location': self.location, } def to_dict(self): """Add custom attributes to SCIM response.""" d = super().to_dict() d.update({ 'urn:example:custom:schema': { 'department': self.obj.department, 'employeeNumber': self.obj.employee_id, }, }) return d def from_dict(self, d): """Parse custom attributes from SCIM request.""" super().from_dict(d) # Handle custom schema extension custom_attrs = d.get('urn:example:custom:schema', {}) if custom_attrs.get('department'): self.obj.department = custom_attrs['department'] if custom_attrs.get('employeeNumber'): self.obj.employee_id = custom_attrs['employeeNumber'] def handle_replace(self, path, value, operation): """Handle PATCH replace operations.""" attr_map = { 'familyName': 'last_name', 'givenName': 'first_name', 'active': 'is_active', 'userName': 'username', 'externalId': 'scim_external_id', } for attr, attr_value in (value or {}).items(): if attr in attr_map: setattr(self.obj, attr_map[attr], attr_value) elif attr == 'emails': self.parse_emails(attr_value) else: raise scim_exceptions.SCIMException('Not Implemented', status=409) self.obj.save() def delete(self): """Soft delete by deactivating user instead of removing.""" self.obj.is_active = False self.obj.save() # settings.py SCIM_SERVICE_PROVIDER = { 'USER_ADAPTER': 'myapp.adapters.CustomSCIMUser', # ... other settings } ``` ## Custom User Model with SCIM Mixins Create a Django user model with required SCIM fields using the provided abstract mixins. ```python # models.py from django.contrib.auth.models import AbstractBaseUser, PermissionsMixin from django.db import models from django_scim.models import AbstractSCIMUserMixin, AbstractSCIMGroupMixin class User(AbstractSCIMUserMixin, AbstractBaseUser, PermissionsMixin): """Custom user model with SCIM support. AbstractSCIMUserMixin provides: - scim_id: Unique SCIM identifier (auto-set on save) - scim_external_id: External ID from identity provider - scim_username: SCIM username field """ email = models.EmailField(unique=True) first_name = models.CharField(max_length=100) last_name = models.CharField(max_length=100) is_active = models.BooleanField(default=True) is_staff = models.BooleanField(default=False) date_joined = models.DateTimeField(auto_now_add=True) USERNAME_FIELD = 'email' @property def scim_groups(self): """Required by AbstractSCIMUserMixin - return user's groups.""" return self.groups.all() class Group(AbstractSCIMGroupMixin, models.Model): """Custom group model with SCIM support. AbstractSCIMGroupMixin provides: - scim_id: Unique SCIM identifier - scim_external_id: External ID from identity provider - scim_display_name: Group display name """ name = models.CharField(max_length=100) members = models.ManyToManyField( 'User', related_name='groups', blank=True ) @property def user_set(self): """Required by SCIMGroup adapter.""" return self.members # settings.py AUTH_USER_MODEL = 'myapp.User' SCIM_SERVICE_PROVIDER = { 'USER_MODEL_GETTER': 'django.contrib.auth.get_user_model', 'GROUP_MODEL': 'myapp.models.Group', # ... other settings } ``` ## Custom Filter Query Implement custom filtering logic to support multi-tenancy or additional SCIM filter attributes. ```python # filters.py from django_scim.filters import UserFilterQuery, GroupFilterQuery class TenantUserFilterQuery(UserFilterQuery): """Custom filter query with multi-tenant support.""" attr_map = { # Map SCIM attributes to database columns ('userName', None, None): 'email', ('name', 'familyName', None): 'last_name', ('familyName', None, None): 'last_name', ('name', 'givenName', None): 'first_name', ('givenName', None, None): 'first_name', ('active', None, None): 'is_active', ('externalId', None, None): 'scim_external_id', # Custom attribute mapping ('emails', 'value', None): 'email', } @classmethod def get_extras(cls, q, request=None): """Add tenant filtering to all queries.""" if request and hasattr(request, 'user') and request.user.is_authenticated: # Filter results to current tenant return ' AND tenant_id = %s', [request.user.tenant_id] return '', [] class TenantGroupFilterQuery(GroupFilterQuery): """Custom group filter with tenant isolation.""" attr_map = { ('displayName', None, None): 'name', ('externalId', None, None): 'scim_external_id', } @classmethod def get_extras(cls, q, request=None): if request and hasattr(request, 'user'): return ' AND tenant_id = %s', [request.user.tenant_id] return '', [] # settings.py SCIM_SERVICE_PROVIDER = { 'USER_FILTER_PARSER': 'myapp.filters.TenantUserFilterQuery', 'GROUP_FILTER_PARSER': 'myapp.filters.TenantGroupFilterQuery', # ... other settings } ``` ## Custom Authentication Middleware Implement custom authentication checking for SCIM endpoints using OAuth2 or other mechanisms. ```python # middleware.py from django.http import HttpResponse from django_scim.middleware import SCIMAuthCheckMiddleware from oauth2_provider.contrib.rest_framework import OAuth2Authentication class OAuth2SCIMAuthCheckMiddleware(SCIMAuthCheckMiddleware): """Custom SCIM auth middleware with OAuth2 support.""" def process_request(self, request): """Authenticate request using OAuth2 before SCIM processing.""" if self.should_log_request(request): self.log_request(request) # Skip auth check for service discovery endpoints public_endpoints = ['/ServiceProviderConfig', '/Schemas', '/ResourceTypes'] if any(request.path.endswith(ep) for ep in public_endpoints): return None # Check for valid OAuth2 token auth = OAuth2Authentication() try: user_auth_tuple = auth.authenticate(request) if user_auth_tuple: request.user, request.auth = user_auth_tuple return None except Exception: pass # Return 401 if not authenticated response = HttpResponse(status=401) response['WWW-Authenticate'] = 'Bearer realm="SCIM"' return response # Alternative: Custom predicate function def custom_is_authenticated(user): """Custom authentication check for SCIM requests.""" if not user or user.is_anonymous: return False # Check for specific permission or scope return user.has_perm('scim.can_provision_users') # settings.py SCIM_SERVICE_PROVIDER = { 'AUTH_CHECK_MIDDLEWARE': 'myapp.middleware.OAuth2SCIMAuthCheckMiddleware', # Or use custom predicate with default middleware: 'GET_IS_AUTHENTICATED_PREDICATE': 'myapp.middleware.custom_is_authenticated', # ... other settings } ``` ## Multi-Tenant Configuration Configure SCIM for multi-tenant applications with dynamic base URL and tenant-scoped queries. ```python # utils.py def get_base_location_from_request(request=None): """Dynamically generate SCIM base URL from request.""" if request: scheme = 'https' if request.is_secure() else 'http' host = request.get_host() return f'{scheme}://{host}/scim/v2/' return 'https://api.example.com/scim/v2/' def get_extra_model_filter_kwargs_getter(model_cls): """Return function that provides tenant filtering kwargs.""" def get_extra_filter_kwargs(request, uuid=None): if hasattr(request, 'user') and request.user.is_authenticated: return {'tenant_id': request.user.tenant_id} return {} return get_extra_filter_kwargs def get_extra_model_exclude_kwargs_getter(model_cls): """Return function that provides exclusion kwargs.""" def get_extra_exclude_kwargs(request): # Exclude system/service accounts from SCIM return {'is_system_user': True} return get_extra_exclude_kwargs # settings.py SCIM_SERVICE_PROVIDER = { 'NETLOC': 'api.example.com', 'SCHEME': 'https', 'BASE_LOCATION_GETTER': 'myapp.utils.get_base_location_from_request', 'GET_EXTRA_MODEL_FILTER_KWARGS_GETTER': 'myapp.utils.get_extra_model_filter_kwargs_getter', 'GET_EXTRA_MODEL_EXCLUDE_KWARGS_GETTER': 'myapp.utils.get_extra_model_exclude_kwargs_getter', 'USER_ADAPTER': 'myapp.adapters.CustomSCIMUser', 'GROUP_MODEL': 'myapp.models.Group', 'GROUP_ADAPTER': 'django_scim.adapters.SCIMGroup', 'USER_FILTER_PARSER': 'myapp.filters.TenantUserFilterQuery', 'GROUP_FILTER_PARSER': 'myapp.filters.TenantGroupFilterQuery', 'SERVICE_PROVIDER_CONFIG_MODEL': 'django_scim.models.SCIMServiceProviderConfig', 'AUTHENTICATION_SCHEMES': [ { 'type': 'oauth2', 'name': 'OAuth 2', 'description': 'OAuth 2 with bearer token', 'specUri': 'https://tools.ietf.org/html/rfc6750', }, ], 'EXPOSE_SCIM_EXCEPTIONS': False, # Hide internal errors in production 'WWW_AUTHENTICATE_HEADER': 'Bearer realm="SCIM 2.0"', } ``` ## Summary django-scim2 provides a complete SCIM 2.0 provider implementation that integrates seamlessly with Django's authentication system. The library is ideal for SaaS applications requiring enterprise SSO integrations, allowing identity providers like Okta, Azure AD, and OneLogin to automatically provision and deprovision users. Key features include full CRUD operations for Users and Groups, SCIM filter query support, configurable adapters for custom user models, and extensible middleware for authentication. The architecture follows Django conventions with customizable adapters, filter parsers, and middleware components. For production deployments, implement custom adapters to map your specific user model fields, configure tenant isolation through filter kwargs getters, and integrate with your existing OAuth2 or authentication system. The library handles the complexity of SCIM protocol compliance while providing hooks for application-specific business logic such as soft deletes, custom schemas, and multi-tenant data isolation.