SCIM โ Integration Guide
Overview
The scim subgraph is a dedicated Rust/Axum microservice that implements the SCIM 2.0 protocol (RFC 7643/7644) for automated user and group provisioning from enterprise Identity Providers โ Okta, Azure AD, Google Workspace, and others.
Unlike the other subgraphs, the SCIM service is a pure REST API โ it does not participate in the GraphQL federation or the Apollo gateway. Instead, it is called directly by the IdP using SCIM-standard bearer token authentication.
Key distinction: SCIM handles automated provisioning (IdP pushes user lifecycle changes). The Auth & Identity service handles interactive authentication (Visitors logging in). Together with Corporate Identity (SAML & SCIM), they form the enterprise identity stack.
Architecture at a Glance
| Component | Technology | Port |
|---|---|---|
| REST API | Axum | 3019 |
| Database | PostgreSQL (ngwenya_auth โ shared) |
โ |
| HTTP Client | reqwest (for Organizations delegation) | โ |
Position in the Federation
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ IdP (Okta / Azure AD / Google) โ
โโโโโโโโโโโโฌโโโโโโโโโโโโโโโโโโโโโโโโ
โ SCIM 2.0 REST
โ Authorization: Bearer <scim_token>
โผ
โโโโโโโโโโโโโโโโ
โ scim :3019 โ Rust/Axum โ pure REST, no GraphQL
โโโโโโโโฌโโโโโโโโ
โ
โโโโโโโดโโโโโโ
โผ โผ
โโโโโโโโโโโ โโโโโโโโโโโโโโโโ
โ auth โ โorganizations โ
โ :3008 โ โ :3009 โ
โ Postgresโ โ MongoDB โ
โโโโโโโโโโโ โโโโโโโโโโโโโโโโ
The SCIM service:
- Reads from the shared
ngwenya_authPostgres DB (users, scim_tokens) - Writes to the same DB (user creation, membership tracking)
- Will call the Organizations service via HTTP for Team/Group operations
Core Concepts
SCIM Tokens
SCIM bearer tokens are per-Organization credentials generated via the Auth subgraph's GraphQL API. They allow an IdP to authenticate with the SCIM service on behalf of a specific Organization.
Token lifecycle:
- Organization admin calls
generateScimToken(orgId, label)on the Auth GraphQL API - Auth service generates a 48-character random token
- A SHA-256 hash is stored in the
scim_tokensPostgres table โ the raw token is never persisted - The raw token is returned once to the admin
- Admin pastes the token into their IdP's SCIM configuration
- On every SCIM request, the service hashes the bearer token and looks up the hash
# Generate a SCIM token (Auth subgraph)
mutation {
generateScimToken(orgId: "org-abc123", label: "Okta SCIM") {
token # โ shown ONCE โ copy it now
id
label
}
}
# List active tokens
query {
scimTokens(orgId: "org-abc123") {
id
label
lastUsedAt
createdAt
}
}
# Revoke a token
mutation {
revokeScimToken(orgId: "org-abc123", tokenId: "token-id")
}
SCIM User โ Platform User Mapping
| SCIM Field | Platform Field | Notes |
|---|---|---|
userName |
email |
Primary identifier โ must be a valid email |
name.givenName |
display_name (first part) |
Combined with familyName |
name.familyName |
display_name (last part) |
Combined with givenName |
emails[].value |
email |
Primary email is used |
active |
Org membership status | false โ removed from Organization |
externalId |
Stored on membership | IdP's internal identifier |
SCIM Group โ Team Mapping
Groups in SCIM map to Teams in the Organizations service:
| SCIM Field | Team Field | Notes |
|---|---|---|
displayName |
name |
Team display name |
externalId |
externalId |
IdP group identifier (sparse unique index) |
members |
TeamMembership |
Member list synced from IdP |
REST API Reference
Discovery Endpoints (No Auth Required)
| Method | Path | Description |
|---|---|---|
GET |
/health |
Liveness probe |
GET |
/scim/v2/ServiceProviderConfig |
Service capabilities (patch, filter, etc.) |
GET |
/scim/v2/Schemas |
User and Group schema definitions |
GET |
/scim/v2/ResourceTypes |
Available resource type metadata |
User Endpoints (Bearer Token Required)
| Method | Path | Description |
|---|---|---|
GET |
/scim/v2/Users |
List users (supports filter=userName eq "x") |
GET |
/scim/v2/Users/:id |
Get a single user by UUID |
POST |
/scim/v2/Users |
Create a user + Organization membership |
PUT |
/scim/v2/Users/:id |
Replace a user (full update) |
PATCH |
/scim/v2/Users/:id |
Partial update (active, displayName, userName) |
DELETE |
/scim/v2/Users/:id |
Deactivate โ remove from Organization |
Group Endpoints (Bearer Token Required)
| Method | Path | Description |
|---|---|---|
GET |
/scim/v2/Groups |
List groups (โ Teams) |
GET |
/scim/v2/Groups/:id |
Get a group |
POST |
/scim/v2/Groups |
Create a group (โ Team) |
PUT |
/scim/v2/Groups/:id |
Replace a group |
PATCH |
/scim/v2/Groups/:id |
Partial update (add/remove members) |
DELETE |
/scim/v2/Groups/:id |
Delete a group |
Request / Response Examples
Create a User
curl -X POST http://localhost:3019/scim/v2/Users \
-H "Authorization: Bearer YOUR_SCIM_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "jdoe@example.com",
"name": { "givenName": "Jane", "familyName": "Doe" },
"emails": [{ "value": "jdoe@example.com", "primary": true }],
"active": true
}'
Response (201 Created):
{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "550e8400-e29b-41d4-a716-446655440000",
"userName": "jdoe@example.com",
"name": { "givenName": "Jane", "familyName": "Doe" },
"emails": [{ "value": "jdoe@example.com", "primary": true }],
"active": true,
"meta": {
"resourceType": "User",
"created": "2026-04-08T22:00:00Z",
"lastModified": "2026-04-08T22:00:00Z",
"location": "/scim/v2/Users/550e8400-e29b-41d4-a716-446655440000"
}
}
Filter Users
# Find user by email
curl -H "Authorization: Bearer TOKEN" \
"http://localhost:3019/scim/v2/Users?filter=userName%20eq%20%22jdoe@example.com%22"
Deactivate a User (PATCH)
curl -X PATCH http://localhost:3019/scim/v2/Users/USER_UUID \
-H "Authorization: Bearer TOKEN" \
-H "Content-Type: application/json" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [{ "op": "replace", "path": "active", "value": false }]
}'
Setting active: false removes the user from the Organization while preserving their platform account.
Error Handling
All errors follow the SCIM error schema (RFC 7644 ยง3.12):
{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:Error"],
"detail": "Human-readable error message",
"status": "404"
}
| Status | Scenario |
|---|---|
201 |
User/group created successfully |
200 |
User/group returned or updated |
204 |
User/group deleted |
400 |
Invalid request body or filter syntax |
401 |
Missing or invalid bearer token |
404 |
Resource not found |
409 |
Uniqueness conflict (duplicate email) |
Database Schema
The SCIM service shares the ngwenya_auth PostgreSQL database with the Auth service. SCIM-specific tables are created by auth migrations:
| Table | Purpose |
|---|---|
scim_tokens |
Bearer token hashes, org scoping, revocation tracking |
users |
Platform user records (shared with auth) |
Migration: apps/auth/migrations/20260408000000_saml_scim.sql
IdP Configuration Guides
Okta
- Add โ Application โ SCIM 2.0 Test App
- Set Base URL:
https://scim.mallnline.com/scim/v2 - Set Auth Mode: Bearer Token โ paste SCIM token
- Enable provisioning: Create Users, Update User Attributes, Deactivate Users
- Test connector โ verify green status
Azure AD
- Enterprise Applications โ New Application โ Non-gallery
- Provisioning โ Automatic โ Tenant URL:
https://scim.mallnline.com/scim/v2 - Secret Token: paste SCIM token
- Test Connection โ verify success
- Start provisioning
Google Workspace
- Apps โ SAML Apps โ Auto-provisioning
- SCIM endpoint:
https://scim.mallnline.com/scim/v2 - Bearer token: paste SCIM token
- Map attributes: email โ userName
Environment Variables
| Variable | Required | Description |
|---|---|---|
DATABASE_URL |
โ | PostgreSQL connection (shared ngwenya_auth DB) |
SCIM_SERVICE_PORT |
โ | HTTP port (default: 3019) |
ORGANIZATIONS_SERVICE_URL |
โ | Organizations service URL (default: http://localhost:3009) |
RUST_LOG |
โ | Tracing filter (default: scim=debug,tower_http=debug) |
File Reference
| File | Purpose |
|---|---|
apps/scim/src/main.rs |
Server entry โ auth middleware, routing, port binding |
apps/scim/src/config.rs |
Environment configuration |
apps/scim/src/middleware.rs |
Bearer token auth (SHA-256 validation + org_id injection) |
apps/scim/src/models.rs |
RFC 7643/7644 types (User, Group, ListResponse, PatchOp, Error) |
apps/scim/src/routes.rs |
SCIM REST handlers (Users CRUD, Groups CRUD, Discovery) |
apps/scim/src/db.rs |
Shared Postgres pool |
Testing
| Suite | Tests | Coverage |
|---|---|---|
| Token hashing | 3 | Determinism, length, uniqueness |
| SCIM types | 7 | User/Group/Error/ListResponse serialization |
| Filter parsing | 3 | userName eq valid, case-insensitive, invalid |
| User conversion | 1 | DB row โ SCIM User |
| Discovery | 1 | ServiceProviderConfig structure |
| Total | 15 | โ |
cd apps/scim && cargo test
Related
- Corporate Identity (SAML & SCIM) โ Feature overview of the complete enterprise SSO stack (SAML SP + SCIM provisioning)
- Auth & Identity โ Identity management, session lifecycle โ hosts SCIM token generation and SAML endpoints
- Organizations & Permissions โ The membership and team infrastructure that SCIM provisions into
- Teams & Sub-Groups โ SCIM Groups map to Teams via the
externalIdfield