Developer Docs

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:

  1. Reads from the shared ngwenya_auth Postgres DB (users, scim_tokens)
  2. Writes to the same DB (user creation, membership tracking)
  3. 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:

  1. Organization admin calls generateScimToken(orgId, label) on the Auth GraphQL API
  2. Auth service generates a 48-character random token
  3. A SHA-256 hash is stored in the scim_tokens Postgres table โ€” the raw token is never persisted
  4. The raw token is returned once to the admin
  5. Admin pastes the token into their IdP's SCIM configuration
  6. 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

  1. Add โ†’ Application โ†’ SCIM 2.0 Test App
  2. Set Base URL: https://scim.mallnline.com/scim/v2
  3. Set Auth Mode: Bearer Token โ†’ paste SCIM token
  4. Enable provisioning: Create Users, Update User Attributes, Deactivate Users
  5. Test connector โ†’ verify green status

Azure AD

  1. Enterprise Applications โ†’ New Application โ†’ Non-gallery
  2. Provisioning โ†’ Automatic โ†’ Tenant URL: https://scim.mallnline.com/scim/v2
  3. Secret Token: paste SCIM token
  4. Test Connection โ†’ verify success
  5. Start provisioning

Google Workspace

  1. Apps โ†’ SAML Apps โ†’ Auto-provisioning
  2. SCIM endpoint: https://scim.mallnline.com/scim/v2
  3. Bearer token: paste SCIM token
  4. 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