SCIM provisioning
Automate user create, update, and deactivate from your identity provider with SCIM 2.0.
Issuing and revoking SCIM bearer tokens requires canManageOrgSettings. The SCIM endpoints themselves authenticate with the per-organization bearer token, never a user session.
What SCIM does
SCIM 2.0 (System for Cross-domain Identity Management — RFC 7643 + 7644) lets your identity provider — Microsoft Entra ID, Okta, or any IdP with a SCIM connector — push user lifecycle events directly to Strata. When HR offboards an employee in your IdP, the IdP's SCIM connector sends a PATCH:
{ "Operations": [{ "op": "Replace", "path": "active", "value": false }] }
Within seconds — without any human intervention on the Strata side — the user is deactivated and every active session is revoked. This closes the "I forgot to delete Bob in Strata" compliance gap that bites every multi-app enterprise.
What is implemented today
Pilot integrations should be limited to Microsoft Entra ID first — its SCIM connector is the most forgiving and matches Strata's spec coverage. Okta is supported but uses a richer filter language than Strata's parser handles; basic create / read / deactivate works fine.
Endpoints
| Resource | URL | Auth | What it does |
|---|---|---|---|
| ServiceProviderConfig | GET /scim/v2/ServiceProviderConfig | None | IdP capability discovery. |
| ResourceTypes | GET /scim/v2/ResourceTypes | None | Lists the User and Group resources. |
| Schemas | GET /scim/v2/Schemas, GET /scim/v2/Schemas/urn:ietf:params:scim:schemas:core:2.0:User | None | The User schema Strata implements. |
| List Users | GET /scim/v2/Users | Bearer | Supports userName eq "<email>" and externalId eq "<id>" filters, plus startIndex and count (max 200). |
| Get User | GET /scim/v2/Users/<id> | Bearer | One user. Returns 404 for a user outside your org — Strata never distinguishes "not found" from "exists in another org," so a token can't probe whether a user ID belongs to any other tenant. |
| Create User | POST /scim/v2/Users | Bearer | Pre-creates the user row. Auto-assigns the built-in user role. Returns 201. SSO sign-in remains the entry path; SCIM creation just pre-authorizes. |
| PATCH User | PATCH /scim/v2/Users/<id> | Bearer | Partial update on active, displayName, externalId, userName/emails. Deactivation also revokes all sessions. |
| PUT User | PUT /scim/v2/Users/<id> | Bearer | Full replace. Implemented as a sequence of PATCH replace operations. |
| Groups | GET, POST /scim/v2/Groups and GET, PATCH, PUT, DELETE /scim/v2/Groups/<id> | Bearer | The Group resource. See the note below before enabling group sync in your IdP. |
| Delete User | DELETE /scim/v2/Users/<id> | Bearer | Hard remove. Most IdPs prefer active: false — it preserves the audit trail. |
Supported User attributes
userName(required, must be an email)externalId(your IdP's stable identifier)active(boolean)displayNamename.givenName,name.familyName,name.formattedemails[].value,emails[].type,emails[].primary
Other attributes in incoming requests are silently ignored per RFC 7644 §3.5.2.1.
What is deferred
- Complex filters —
and/or/not,pr(present), attribute paths with dots are not parsed. Only<attr> eq "<value>"works; unsupported filters fall back to "return all" so an IdP does not error out. - ETag concurrency and Bulk operations — not implemented.
SCIM base URL
https://app.strata.kronisys.com/scim/v2
Both the tenant URL in Entra and the connector base URL in Okta should point here. Use the canonical organization domain for your deployment.
Generating a bearer token
SCIM tokens are issued through the admin API. The endpoint requires canManageOrgSettings.
NoteSCIM tokens are managed through the admin API (
/api/admin/scim/tokens), shown below — there is no separate token screen in the admin console.
- Call
POST /api/admin/scim/tokenswith a JSON body{ "label": "Entra production sync" }. The label is optional and capped at 120 characters; it defaults toSCIM token. - The response returns the token once in plaintext, prefixed with
scim_and base64url-encoded (32 bytes of entropy = 256 bits). - Copy it immediately. Strata stores only a SHA-256 hash; if you lose the token there is no recovery path — revoke and issue a new one.
- Paste it into your IdP's SCIM connector configuration.
The response also includes a configuration cheatsheet:
{
"ok": true,
"token": "scim_…",
"token_id": "…",
"label": "Entra production sync",
"configure": {
"tenant_url": "https://app.strata.kronisys.com/scim/v2",
"secret_token": "scim_…",
"notice": "This token is shown ONCE. Store it in your IdP's SCIM connector now. If lost, issue a new token and revoke this one."
}
}
Issuing a token is audited as scim.token_issued with the token label and ID.
Configuring Microsoft Entra ID (recommended)
- In Entra ID, go to Enterprise applications → New application → Create your own application. Name it
Strata. Select Integrate any other application you don't find in the gallery (Non-gallery). - After creation, go to Provisioning → Get started → Provisioning Mode: Automatic.
- Fill in:
- Tenant URL:
https://app.strata.kronisys.com/scim/v2 - Secret Token: paste the
scim_…token you generated in Strata.
- Tenant URL:
- Click Test Connection. Entra should report success — this calls
GET /scim/v2/Userswith the bearer. - Save. Configure Mappings to map your Entra attributes to the SCIM User attributes Strata supports (defaults are sensible —
userName→mail,active→accountEnabled, etc.). - Assign users or groups under Provisioning → Users and groups and turn Provisioning Status to On.
NoteAssigning a group here is the normal way to scope provisioning, and Entra will provision that group's members as Strata users regardless of whether you also sync the group object itself. Strata now implements the SCIM
/Groupsresource, but group sync has not been validated end to end against Entra — see Push Groups below before switching it on.
Entra will run an initial cycle, then sync changes every ~40 minutes. Forced cycles run in 10–15 seconds.
Configuring Okta
Okta's SCIM connector lives at Applications → your app → Provisioning → Integration. The configuration is similar but the filter for User import uses syntax Strata does not parse (pr, complex AND). Stick to:
- Create Users — works.
- Update User Attributes — works.
- Deactivate Users — works (sends
active: false). - Push Groups — the PATCH operations Entra and Okta actually send are handled correctly, verified by execution against the router: adding members, removing a member by filter, renaming a group, and an authoritative replace where members you did not list are removed rather than left behind. Memberships an IdP creates are recorded as IdP-sourced, not manual.
A full tenant sync is still unproven — no real IdP has completed one end to end, so the provisioning sequence (probe-then-create ordering, retry behaviour) has not been exercised. Treat group sync as ready to trial against your own tenant, not yet as a supported configuration.
WarningAn IdP may only modify groups it created. A group you create by hand in Strata is administered in Strata: any IdP write against it is refused with
403andscimType: "mutability", before anything is changed. So if you create Engineering in Strata and then point Entra at a group of the same name, Entra will report failures with no obvious cause. Let the IdP create the groups it intends to manage, and keep hand-made groups out of its scope.
What happens when a user is deactivated
When the IdP fires {"op":"Replace","path":"active","value":false}, Strata routes the change through the same internal path the admin console uses — so the SCIM path enforces every invariant the UI does:
- The org's last remaining admin can never be deactivated. If the request targets the final admin, Strata refuses it with
409 Conflict(scimType: mutability) — promote another user to admin first. An IdP misconfiguration or over-eager offboarding rule can't lock the org out. - The
users.is_activecolumn flips to0. - Every session token belonging to that user is revoked. The next request from the user is rejected.
- An audit entry is written with
action = user.disabledandactor_email = scim:<token-label>(re-activation writesaction = user.enabled). The all-zeros system UUID is recorded as the actor, since no human is behind a SCIM token.
Re-activation (active: true) reverses the is_active flip but does not restore the revoked sessions — the user re-authenticates via Microsoft SSO on their next visit.
WarningString booleans are coerced. Some Entra connector versions emit
"false"/"False"/"0"foractive. Strata treats these asfalse, so an offboarded employee is never silently left active by a string-typed value.
Email changes via SCIM
PATCH-ing userName or emails changes the email on the users row. The SSO match key is the user's email, so changing it via SCIM could detach the user from their existing Microsoft account if their Microsoft email also changed. Strata logs a [SCIM] Email change applied to <user>: <email> (verify SSO match key) warning so an operator can spot-check.
The recommended pattern is: change the email in Microsoft first, then let SCIM re-sync the new value to Strata.
Token security
- Tokens are per-organization. A token issued for Org A can never touch Org B's users — the bearer middleware verifies the org binding on every request and the SCIM handlers scope every query to that org.
- Tokens are stored as
sha256hashes only. The plaintext is shown once at issuance. - Tokens never expire automatically. Rotate proactively at least annually, or whenever the IdP that holds the token is reconfigured.
- The Authorization header is
Authorization: Bearer scim_<base64url>. - The SCIM endpoints are rate-limited (per token, with an IP fallback) and apply a short lockout after repeated authentication failures, returning
429when the limit is hit.
Revoking a token
Revoke a token by calling DELETE /api/admin/scim/tokens/<token-id> (canManageOrgSettings required). The token's revoked_at column is set; the next request from that token returns 401 Token invalid or revoked. The revocation is audited as scim.token_revoked.
You can have multiple tokens active at once — for example, one for production and one for a staging IdP — and revoke them independently.
What can go wrong
| Failure | What it means |
|---|---|
401 invalidCredentials Bearer token required. | The request is missing the Authorization: Bearer … header. |
401 invalidCredentials Empty bearer token. | The bearer header is present but the token is empty. |
401 invalidCredentials Token invalid or revoked. | The token does not hash to any row, or its revoked_at is set. |
404 User not found | The user UUID does not exist in your org (including a user that exists in another org). |
409 uniqueness User <email> already exists | POST tried to create a user whose email is already in the org. Use PATCH instead. |
409 mutability Refusing to deactivate the last remaining administrator… | PATCH/PUT/DELETE would remove the org's only admin. Promote another user to admin first. |
400 invalidValue Operations array required | PATCH body has no Operations array. |
400 invalidValue userName must be an email | POST body's userName does not contain @. |
429 Too many failed authentication attempts / Rate limit exceeded. | The token (or IP) tripped the auth-failure lockout or the request rate limit. Back off and retry. |
| IdP filter that worked elsewhere returns all users | Filter parser only handles <attr> eq "<value>". Anything more complex falls back to "return all". |
See Users for how SCIM-created users look in the admin console, and Audit log for the scim.token_issued, scim.token_revoked, and user.disabled (scim: actor) trail.