Aller au contenu principal

SCIM 2.0 Provisioning

SyAuth ships a SCIM 2.0 server so enterprise identity providers — Okta, Azure AD / Entra ID, OneLogin, JumpCloud, and any other SCIM-capable IdP — can push their directory of users and groups into a SyAuth workspace and keep it in sync automatically.

When an HR system onboards a new hire and adds them to an IdP group, SyAuth gets a SCIM User POST and a Group PATCH within seconds. When someone leaves, SyAuth gets a DELETE (which deactivates the user) or a PATCH flipping active=false. No manual CSV imports, no forgotten off-boarding.


End-to-end setup (5 minutes)

1. Mint a SCIM token

  1. Open SCIM Provisioning in the SyAuth dashboard (/security/scim).
  2. Label it ("Okta production"), optionally set an expiry, click Create token.
  3. Copy the plaintext token — it is only shown once.

2. Point your IdP at SyAuth

Configure your IdP's SCIM provisioning with:

FieldValue
Base URLhttps://api.syauth.com/scim/v2
AuthorizationBearer <your token>
Token (Azure AD)the plaintext token
Supported operationsCreate, Update (PATCH + PUT), Deactivate

That's it. The IdP will discover capabilities via /ServiceProviderConfig, /ResourceTypes, and /Schemas.

3. Provision

Assign a user or group to the SyAuth app in your IdP. Within seconds you'll see:

  • A new ExtsUser row in the workspace with email_verified=true (SCIM-provisioned users skip the email verification step — the IdP vouches for the address).
  • An ExtsGroup row if the IdP syncs groups, with members populated.
  • scim_user_create / scim_group_create entries in the Audit Log with actor_type=api_token so you can trace everything back to the token that pushed it.

Supported endpoints

All endpoints live under /scim/v2/ and require a Bearer SCIM token. Responses use Content-Type: application/scim+json.

Users

Method & pathPurpose
GET /UsersList (with pagination + RFC 7644 filter)
POST /UsersCreate
GET /Users/{id}Read
PUT /Users/{id}Replace
PATCH /Users/{id}Partial update (add / replace / remove)
DELETE /Users/{id}Deactivate (soft) — is_active=false, all group memberships removed

Groups

Method & pathPurpose
GET /GroupsList
POST /GroupsCreate
GET /Groups/{id}Read
PUT /Groups/{id}Replace (including membership)
PATCH /Groups/{id}Add / remove members, rename
DELETE /Groups/{id}Delete

Discovery

PathReturns
/ServiceProviderConfigFeature support matrix — patch ✓, filter ✓, bulk ✗, sort ✗, etag ✗
/ResourceTypesUser and Group
/SchemasCore User + Group schema definitions

Supported filter grammar

A minimal but safe subset of RFC 7644 §3.4.2.2:

filter := term ( OR term )*
term := factor ( AND factor )*
factor := "(" filter ")" | "not(" filter ")" | attr op value | attr "pr"
op := eq | ne | co | sw | ew | gt | ge | lt | le

Everything Okta and Azure AD send in practice works:

userName eq "[email protected]"
externalId eq "00u1a..."
active eq true
displayName sw "Engineering"
emails.value co "@acme.com" and active eq true

Unknown attributes return HTTP 400 invalidFilter rather than a silent empty set.


SCIM ↔ SyAuth attribute mapping

User

SCIM attributeSyAuth fieldNotes
userNameExtsUser.emailLowercased on write; unique per workspace via the existing (email, oauth_client) constraint
name.givenNamefirst_nameTruncated to 30 chars
name.familyNamelast_nameTruncated to 30 chars
emails[primary].valueExtsUser.emailOverrides userName if primary flagged
activeis_activefalse deactivates without deleting
externalIdExtsUser.id (UUID)We echo the internal UUID so IdPs can reconcile

Unknown attributes (title, preferredLanguage, phoneNumbers, addresses, enterprise:2.0:User.department…) are accepted silently so IdPs don't retry forever, but they are not stored today.

Group

SCIM attributeSyAuth fieldNotes
displayNameExtsGroup.nameUnique per workspace
members[*].valueExtsUserGroup rowsOnly users that already exist in the workspace are added

Example: create a user

curl -X POST "https://api.syauth.com/scim/v2/Users" \
-H "Authorization: Bearer scim_…" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"userName": "[email protected]",
"name": { "givenName": "Alice", "familyName": "Example" },
"emails": [ { "value": "[email protected]", "primary": true, "type": "work" } ],
"active": true
}'

Response 201 Created with Location: /scim/v2/Users/{id}:

{
"schemas": ["urn:ietf:params:scim:schemas:core:2.0:User"],
"id": "…",
"externalId": "…",
"userName": "[email protected]",
"name": { "givenName": "Alice", "familyName": "Example", "formatted": "Alice Example" },
"emails": [ { "value": "[email protected]", "primary": true, "type": "work" } ],
"active": true,
"meta": { "resourceType": "User", "location": "https://api.syauth.com/scim/v2/Users/…" }
}

Example: PATCH to deactivate

curl -X PATCH "https://api.syauth.com/scim/v2/Users/{id}" \
-H "Authorization: Bearer scim_…" \
-H "Content-Type: application/scim+json" \
-d '{
"schemas": ["urn:ietf:params:scim:api:messages:2.0:PatchOp"],
"Operations": [ { "op": "replace", "path": "active", "value": false } ]
}'

Example: filter with pagination

curl -G "https://api.syauth.com/scim/v2/Users" \
-H "Authorization: Bearer scim_…" \
--data-urlencode 'filter=active eq true and emails.value co "@acme.com"' \
--data-urlencode 'startIndex=1' \
--data-urlencode 'count=50'

IdP-specific notes

Okta

  • Set SCIM connector base URL to https://api.syauth.com/scim/v2.
  • Unique identifier field = userName.
  • Supported provisioning actions: Create, Update, Deactivate — leave Sync Password unchecked (SyAuth does not accept passwords via SCIM).
  • Group Push works out of the box.

Azure AD / Entra ID

  • Set Tenant URL to https://api.syauth.com/scim/v2.
  • Set Secret Token to your plaintext SCIM token.
  • Aadoptscim compatibility flag is not required — SyAuth speaks stock SCIM 2.0.
  • Default attribute mappings work; disable the phoneNumbers mapping if you don't want a noisy audit log.

JumpCloud / OneLogin

  • Use the generic SCIM 2.0 connector with the same base URL + bearer token.

Audit events

Every SCIM write is logged to the workspace Audit Log with actor_type=api_token:

EventSeverityWhen
scim_token_createwarnNew SCIM token minted in the dashboard
scim_token_revokewarnToken revoked
scim_user_createinfoIdP provisioned a new user
scim_user_updateinfoPUT or PATCH changed a user
scim_user_deletewarnIdP deactivated a user
scim_group_createinfoNew group
scim_group_updateinfoMembership or rename
scim_group_deletewarnGroup deleted

Security notes

  • Tokens are workspace-scoped. A compromised token can only act inside the workspace it was issued for — it cannot escalate to a different tenant.
  • Stored as SHA-256. Plaintext is generated server-side with a scim_ prefix and 40 bytes of secrets.token_urlsafe entropy. Only the hash persists; the plaintext is shown once in the create response.
  • Revocation is instant. DELETE /developer/scim/tokens/{id}/ flips is_active=false and sets revoked_at; the next SCIM call rejects with 401.
  • Expiry is optional but recommended for machine-to-machine integrations; rotate yearly at minimum.
  • Audit trail is immutable. Every write goes into the append-only audit log with the token id recorded as the actor, so you can always answer "who pushed this change?"
  • No password provisioning. SyAuth ignores password in inbound SCIM payloads. Authentication happens through your IdP flow or SyAuth's native flows.

Limits and what's explicitly not supported

These are by design and documented to set IdP expectations:

  • Bulk operations — not supported. bulk.supported=false in ServiceProviderConfig. Most IdPs fall back to sequential writes automatically.
  • Sorting — not supported. Clients should paginate with startIndex + count.
  • ETag / versioning — not supported.
  • Password sync — deliberately rejected. See above.
  • Enterprise schema — parsed but not persisted. Planned.
  • Custom schemas — not supported.

Max page size is 200 resources per response.