← Documentation

SCIM Directory Sync

Per-account SCIM 2.0 configuration for automated user and group provisioning from a customer's IdP.

Configuration model

Each Account can have a ScimConfig row describing how its customer's identity provider (Okta, Entra ID, etc.) should push directory changes into RealmSSO.

FieldPurpose
enabledWhether SCIM sync is turned on for this account
endpointThe SCIM base URL published back to the customer
bearerTokenHashThe SHA-256 hash of the token the customer's IdP authenticates with. Requests are matched against this hash; the raw token is never persisted (the legacy plain bearerToken column is left unwritten — see Encryption at Rest)
attributeMappingJSON mapping of SCIM attributes to RealmSSO/Keycloak user fields
autoProvisionCreate a user automatically on first SCIM push (defaults to true)
autoDeprovisionDeactivate a user automatically on SCIM delete (defaults to false)
groupSyncSync SCIM groups as well as users (defaults to false)
lastSyncAt / lastSyncStatusBookkeeping for the most recent sync attempt

Endpoints

SCIM routes are mounted at /api/scim/v2 (see Architecture Overview for why that needs its own content-type parser). Every request authenticates with a bearer token looked up against ScimConfig.bearerTokenHash — there's no account id in the path, the token alone identifies the tenant.

GET/api/scim/v2/Users
POST/api/scim/v2/Users
GET/api/scim/v2/Users/:id
PATCH/api/scim/v2/Users/:id
DELETE/api/scim/v2/Users/:id
GET/api/scim/v2/Groups
POST/api/scim/v2/Groups
PATCH/api/scim/v2/Groups/:id
DELETE/api/scim/v2/Groups/:id
GET/api/scim/v2/ServiceProviderConfig
GET/api/scim/v2/Schemas

What the routes do, and what the defaults mean

The SCIM routes persist. A push writes ScimUser, ScimGroup, and ScimGroupMember rows in RealmSSO's own database, and — when the matching config flag is on — creates, updates, or deactivates the corresponding object in the account's Keycloak realm. attributeMapping is resolved against the incoming body, and autoProvision, autoDeprovision, and groupSync are each read at the point they apply. Every request also records an observability event (scim.provisioned, scim.updated, scim.deprovisioned, and their group equivalents).

The defaults are the part worth reading twice. A fresh ScimConfig has autoProvision: true but autoDeprovision: false and groupSync: false. Out of the box, therefore, a customer's IdP creates users in Keycloak but deactivating a user in their directory does not deactivate the Keycloak account, and group membership is not mirrored into the realm. If offboarding through the directory is the reason you want SCIM, you must set autoDeprovision yourself.

Why the response shapes matter

The contract has to be exactly right before any of the provisioning above is reachable. /ServiceProviderConfig and /Schemas are what an IdP's SCIM connector introspects to decide whether patch, filtering, and bulk operations are supported, and both are unauthenticated for that reason. application/scim+json parsing also has to work, or Fastify rejects every SCIM write with a 415 before a handler ever runs — see Architecture Overview for that content-type parser. A connector that mis-reads either one will not attempt the calls that do the work.