Skip to content

Repository files navigation

ITL.Scim.Server

A production-ready SCIM 2.0 server for testing and developing Azure Entra ID provisioning integrations.

Overview

This server implements the System for Cross-domain Identity Management (SCIM) 2.0 specification, enabling secure user and group provisioning from Azure Entra ID to your applications.

Features:

  • Full SCIM 2.0 compliance (users, groups, resource types, service provider config)
  • PostgreSQL persistence with async SQLAlchemy
  • Dual authentication: Bearer Token (testing) + OAuth2 OIDC (production)
  • Azure Entra ID compatible endpoints and error responses
  • Docker Compose for local testing
  • Comprehensive audit logging with structlog
  • Enterprise layered architecture (services, repositories, domain models)

Architecture

src/itl_scim/
├── core/              # Base classes, protocols, exceptions, config
├── domain/            # Pure domain objects (User, Group)
├── schemas/           # Pydantic request/response models
├── models/            # SQLAlchemy ORM models (XRow naming)
├── services/          # Business logic (UserService, GroupService)
├── repositories/      # Data access (UserRepository, GroupRepository)
├── middleware/        # Auth, logging, error handling
├── api/v1/           # FastAPI routes (thin wrappers)
└── infrastructure/    # External adapters (DB client, auth)

Quick Start

Prerequisites

  • Python 3.12+
  • PostgreSQL 14+
  • Docker & Docker Compose (optional, for local testing)

Local Development (with Docker)

cd d:\repos\ITL.Scim.Server

# Start PostgreSQL and run migrations
docker compose up -d

# Install dependencies
pip install -e ".[dev]"

# Run migrations
alembic upgrade head

# Start server
uvicorn itl_scim.main:app --reload --port 8000

Server will be available at http://localhost:8000

Configuration

Create .env in the project root:

# Database
DATABASE_URL=postgresql+asyncpg://scim:scim@localhost:5432/scim

# Auth
SCIM_BEARER_TOKEN=test-token-12345
OAUTH2_ISSUER_URL=https://login.microsoftonline.com/{tenant-id}/v2.0

# Server
DEBUG=true
LOG_LEVEL=INFO

SCIM Endpoints

Users

  • GET /scim/v2/Users — List all users (with filtering, sorting, pagination)
  • GET /scim/v2/Users/{id} — Get single user
  • POST /scim/v2/Users — Create user
  • PUT /scim/v2/Users/{id} — Replace user
  • PATCH /scim/v2/Users/{id} — Partial update user
  • DELETE /scim/v2/Users/{id} — Delete user

Groups

  • GET /scim/v2/Groups — List all groups
  • GET /scim/v2/Groups/{id} — Get single group
  • POST /scim/v2/Groups — Create group
  • PUT /scim/v2/Groups/{id} — Replace group
  • PATCH /scim/v2/Groups/{id} — Partial update group
  • DELETE /scim/v2/Groups/{id} — Delete group

Service Provider Config

  • GET /scim/v2/ServiceProviderConfig — SCIM compliance info
  • GET /scim/v2/ResourceTypes — Available resource types
  • GET /scim/v2/Schemas — SCIM schemas

Azure Entra ID Setup

Step 1: Register Application

  1. Open Azure Portal
  2. Go to Azure Entra IDApp registrationsNew registration
  3. Register your app:
    • Name: SCIM Server Test
    • Supported account types: Single tenant
    • Redirect URI: (leave blank for now)

Step 2: Create Client Secret

  1. In your app registration, go to Certificates & secrets
  2. New client secret → Copy the Value (this is your secret)

Step 3: Configure API Exposure

  1. Go to Expose an API
  2. Click Set next to Application ID URI
  3. Accept the default URI (e.g., api://12345678-...)
  4. Save

Step 4: Grant Admin Consent

Your tenant admin must grant consent. Construct this URL (replace placeholders):

https://login.microsoftonline.com/{tenant-id}/oauth2/v2.0/authorize
  ?client_id={application-id}
  &scope=api://{application-id}/.default
  &response_type=code
  &redirect_uri=https://localhost:8000/auth/callback

Or use Azure Portal API permissionsGrant admin consent for [org].

Step 5: Add SCIM App in Entra ID

  1. Go to Enterprise applicationsNew applicationCreate your own application
  2. Name it SCIM Test, choose Integrate any other application
  3. Go to the new app → Provisioning
  4. Provisioning Mode: Automatic
  5. Tenant URL: http://localhost:8000/scim/v2 (or your server URL)
  6. Secret Token: Your Bearer token from .env (SCIM_BEARER_TOKEN)
  7. Click Test Connection
  8. Once connected, configure Attribute Mappings and Save

Step 6: Assign Users/Groups

  1. In the Entra ID app provisioning config, go to Users and groups
  2. Assign users or groups you want to provision
  3. Go back to Provisioning and click Start provisioning

Testing Endpoints

Using curl

# Set variables
TOKEN="test-token-12345"
BASE_URL="http://localhost:8000/scim/v2"

# List users
curl -H "Authorization: Bearer $TOKEN" "$BASE_URL/Users"

# Create user
curl -X POST "$BASE_URL/Users" \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/scim+json" \
  -d '{
    "schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
    "userName": "alice.smith@contoso.com",
    "name": {
      "givenName": "Alice",
      "familyName": "Smith"
    },
    "emails": [{"value": "alice.smith@contoso.com", "primary": true}],
    "active": true
  }'

# Get user by ID
curl -H "Authorization: Bearer $TOKEN" "$BASE_URL/Users/123e4567-e89b-12d3-a456-426614174000"

# Delete user
curl -X DELETE "$BASE_URL/Users/123e4567-e89b-12d3-a456-426614174000" \
  -H "Authorization: Bearer $TOKEN"

Using Postman

  1. Import SCIM 2.0 Postman collection
  2. Set environment variables:
    • base_url = http://localhost:8000
    • token = Your Bearer token
  3. Run requests against /scim/v2 endpoints

Authentication

Bearer Token (Testing)

For development and testing, Bearer token authentication is simple:

curl -H "Authorization: Bearer test-token-12345" http://localhost:8000/scim/v2/Users

OAuth2 OIDC (Production)

For production with Azure Entra ID:

  1. Set OAUTH2_ISSUER_URL in .env
  2. Server validates ID token signature and claims
  3. Uses tenant-scoped OIDC discovery to fetch public keys

Database Migrations

Create New Migration

alembic revision --autogenerate -m "describe what changed"

Apply Migrations

alembic upgrade head

Rollback

alembic downgrade -1

Running Tests

pytest                              # All tests
pytest tests/test_users.py -v      # Specific file
pytest -k "test_create" --cov      # Specific test with coverage

Deployment

Docker Deployment

docker build -t itl-scim:latest .
docker run -p 8000:8000 \
  -e DATABASE_URL="postgresql+asyncpg://user:pass@db:5432/scim" \
  -e SCIM_BEARER_TOKEN="your-secret-token" \
  itl-scim:latest

Kubernetes

See docs/KUBERNETES.md for Helm chart and deployment instructions.

Monitoring & Logging

Logs are structured using structlog and JSON-formatted for easy parsing:

# Watch logs
docker compose logs -f scim-server

# Sample log output:
{
  "event": "user_created",
  "user_id": "550e8400-e29b-41d4-a716-446655440000",
  "user_name": "alice@contoso.com",
  "timestamp": "2026-08-11T14:30:45.123Z",
  "level": "info"
}

Troubleshooting

Connection Test Failed

error="invalid syntax for unique constraint in definition" or 
"Connection refused"

Solution:

  • Verify PostgreSQL is running: docker compose ps
  • Check DATABASE_URL in .env is correct
  • Check logs: docker compose logs db

Bearer Token Rejected

error="Unauthorized - Invalid authentication credentials"

Solution:

  • Ensure Authorization: Bearer <TOKEN> header is present
  • Token must match SCIM_BEARER_TOKEN from .env
  • Check server logs: docker compose logs scim-server

Resource Not Found

error="No resource with ID found" or HTTP 404

Solution:

  • Verify the resource UUID exists: GET /scim/v2/Users
  • Check exact UUID spelling and case (UUIDs are case-insensitive but Postgres is strict)
  • Use valid UUID format: xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx

Development Tips

Debugging Entra ID Sync Issues

  1. Enable detailed logging: LOG_LEVEL=DEBUG in .env
  2. Watch server logs: docker compose logs -f scim-server
  3. Check audit events in Azure Entra ID → Provisioning logs
  4. Test single user creation via API before enabling bulk sync

Performance Tuning

  • Increase connection pool: DATABASE_POOL_SIZE=20 in .env
  • Enable query caching: CACHE_TTL_SECONDS=300
  • Batch operations: Use /scim/v2/Users?count=100 for pagination

Database Inspection

# Connect to PostgreSQL
docker compose exec db psql -U scim -d scim

# Common queries
SELECT id, user_name, created_at FROM users LIMIT 10;
SELECT * FROM groups WHERE name LIKE 'Sales%';

File Structure

ITL.Scim.Server/
├── src/itl_scim/              # Main package
│   ├── __init__.py
│   ├── main.py               # FastAPI app initialization
│   ├── core/
│   │   ├── config.py         # Settings (Pydantic)
│   │   ├── exceptions.py     # Custom exceptions
│   │   ├── base.py           # Base classes & ABCs
│   │   └── __init__.py
│   ├── domain/
│   │   ├── user.py           # User domain entity
│   │   ├── group.py          # Group domain entity
│   │   └── __init__.py
│   ├── schemas/
│   │   ├── user_requests.py  # Pydantic input models
│   │   ├── user_responses.py # Pydantic output models
│   │   ├── group_requests.py
│   │   ├── group_responses.py
│   │   └── __init__.py
│   ├── models/
│   │   ├── user_row.py       # SQLAlchemy User ORM model
│   │   ├── group_row.py      # SQLAlchemy Group ORM model
│   │   └── __init__.py
│   ├── services/
│   │   ├── user_service.py   # User business logic
│   │   ├── group_service.py  # Group business logic
│   │   └── __init__.py
│   ├── repositories/
│   │   ├── user_repository.py   # User data access
│   │   ├── group_repository.py  # Group data access
│   │   └── __init__.py
│   ├── api/v1/
│   │   ├── users.py         # FastAPI user routes
│   │   ├── groups.py        # FastAPI group routes
│   │   ├── service_provider.py  # SCIM config endpoints
│   │   └── __init__.py
│   ├── middleware/
│   │   ├── auth.py          # Authentication
│   │   ├── logging.py       # Request/response logging
│   │   └── __init__.py
│   ├── infrastructure/
│   │   ├── db_client.py     # Database session
│   │   └── __init__.py
│   └── __init__.py
├── alembic/                 # Database migrations
│   ├── env.py
│   ├── script.py.mako
│   └── versions/
├── tests/                   # Pytest test suite
│   ├── test_users.py
│   ├── test_groups.py
│   ├── test_auth.py
│   └── __init__.py
├── docs/                    # Documentation
│   ├── KUBERNETES.md
│   ├── API.md
│   └── ARCHITECTURE.md
├── docker-compose.yml       # Local dev stack
├── Dockerfile              # Container image
├── .env.example            # Environment template
├── pyproject.toml          # Package config
├── README.md               # This file
└── .gitignore

References

License

MIT — See LICENSE file

Support

For issues or questions, please open a GitHub issue or contact platform@itlusions.com.

About

SCIM 2.0 server implementation for identity management testing and integration

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages