Skip to content

Commit d7124cd

Browse files
waleedlatif1claude
andauthored
feat(scim): directory provisioning (SCIM 2.0) (#7567)
Sim-native SCIM 2.0 provisioning for Okta, Microsoft Entra ID, OneLogin, and JumpCloud: Users and Groups CRUD with filters and paging, RFC 7644 discovery and error envelope, provider payload tolerances, bearer tokens with rotation, reversible deactivation, removal with ownership handoff, group mappings onto permission groups, workspace access, and the organization admin role, hourly reconciliation, an activity log, and the settings UI under SSO. Ships with the Enterprise plan on the hosted product; SCIM_ENABLED / ENTERPRISE_ENABLED on self-hosted. Migration 0324_scim_provisioning (expand-only). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01DGXcwsHzYGp6pUDWRtJKpz
1 parent f52da15 commit d7124cd

161 files changed

Lines changed: 38502 additions & 381 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

apps/docs/content/docs/platform/enterprise/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
"index",
55
"self-hosted",
66
"sso",
7+
"scim",
78
"verified-domains",
89
"session-policies",
910
"access-control",
Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
---
2+
title: Directory provisioning (SCIM)
3+
description: Create, update, and deactivate Sim members automatically from your identity provider
4+
---
5+
6+
import { Callout } from 'fumadocs-ui/components/callout'
7+
import { Step, Steps } from 'fumadocs-ui/components/steps'
8+
import { Tab, Tabs } from 'fumadocs-ui/components/tabs'
9+
import { FAQ } from '@/components/ui/faq'
10+
11+
Directory provisioning connects your identity provider to Sim over SCIM 2.0. Your provider creates members when someone joins, updates them when their details change, and deactivates them the moment they leave — without anyone touching Sim.
12+
13+
It pairs with [SSO](/platform/enterprise/sso). SSO proves who someone is when they sign in. Directory provisioning decides who exists and what they can reach, before and after that.
14+
15+
<Callout type="info">
16+
Included with Enterprise plans. Requires [SSO](/platform/enterprise/sso) to be enabled, because provisioning is configured from the SSO settings page, and at least one [verified domain](/platform/enterprise/verified-domains) for your organization. Self-hosted deployments get it with the other enterprise features through `ENTERPRISE_ENABLED=true`, or turn just this feature on or off with `SCIM_ENABLED` and `NEXT_PUBLIC_SCIM_ENABLED`, alongside the [SSO variables](/platform/enterprise/sso#self-hosted-setup).
17+
</Callout>
18+
19+
## What it does
20+
21+
| Your provider does this | Sim does this |
22+
| --- | --- |
23+
| Assigns a person to the Sim app | Creates their account and adds them to your organization as a Member |
24+
| Updates their name or email | Updates the Sim account, and ends their sessions if the address changed |
25+
| Deactivates them | Blocks sign-in and stops their personal API keys. Everything they own, and every grant they hold, is left untouched; shared workspace keys keep working |
26+
| Reactivates them | Restores access exactly as it was |
27+
| Removes them from the app | Removes their organization membership, ends their sessions, deletes their personal API keys, and reassigns what they owned |
28+
| Adds them to a group | Grants whatever that group maps to |
29+
30+
Deactivation is reversible and never destructive. Someone on leave keeps their workflows, their credentials, and their workspace history; they simply cannot sign in.
31+
32+
## Turn it on
33+
34+
<Steps>
35+
36+
<Step>
37+
### Verify your domain
38+
39+
Sim only provisions people whose email is in a domain your organization has verified. See [Verified domains](/platform/enterprise/verified-domains).
40+
41+
This is what stops another tenant's directory from claiming an address it does not own.
42+
</Step>
43+
44+
<Step>
45+
### Enable directory provisioning
46+
47+
In **Settings → SSO → Directory provisioning**, turn it on. Sim shows your SCIM base URL:
48+
49+
```
50+
https://<your-sim-domain>/api/scim/v2
51+
```
52+
</Step>
53+
54+
<Step>
55+
### Issue a token
56+
57+
Choose whether the token expires (never, 90 days, or a year) and select **Issue token**. It appears once — copy it straight into your provider.
58+
59+
Two tokens can be active at a time, so you can rotate without downtime: issue the new one, update your provider, confirm a sync succeeds, then revoke the old one.
60+
</Step>
61+
62+
<Step>
63+
### Configure your provider
64+
65+
<Tabs items={['Okta', 'Microsoft Entra ID', 'OneLogin', 'JumpCloud']}>
66+
<Tab value="Okta">
67+
68+
In your Okta app, open **Provisioning → Integration** and select **Configure API Integration**.
69+
70+
- **SCIM connector base URL**: `https://<your-sim-domain>/api/scim/v2`
71+
- **Unique identifier field for users**: `userName`
72+
- **Supported provisioning actions**: Push New Users, Push Profile Updates, Push Groups
73+
- **Authentication Mode**: HTTP Header, with your Sim token
74+
75+
Select **Test API Credentials**, then save. Under **Provisioning → To App**, enable Create Users, Update User Attributes, and Deactivate Users.
76+
77+
Okta never deletes users over SCIM. Unassigning someone, or deactivating them in Okta, sends a deactivation — which Sim applies as a suspension.
78+
79+
</Tab>
80+
<Tab value="Microsoft Entra ID">
81+
82+
In your enterprise application, open **Provisioning** and set Provisioning Mode to **Automatic**.
83+
84+
- **Tenant URL**: `https://<your-sim-domain>/api/scim/v2`
85+
- **Secret Token**: your Sim token
86+
87+
Select **Test Connection**, then save and start provisioning.
88+
89+
Entra runs an initial cycle over everyone in scope, then incremental cycles roughly every 40 minutes. Removing someone from the app sends a deactivation; a permanent delete in Entra sends a removal about 30 days later.
90+
91+
</Tab>
92+
<Tab value="OneLogin">
93+
94+
Add a **SCIM Provisioner with SAML** app.
95+
96+
- **SCIM Base URL**: `https://<your-sim-domain>/api/scim/v2`
97+
- **SCIM Bearer Token**: your Sim token
98+
99+
Enable provisioning and choose what happens when a user is removed. Suspend maps to a Sim suspension; Delete removes their membership.
100+
101+
</Tab>
102+
<Tab value="JumpCloud">
103+
104+
Add a **Custom SCIM** identity management integration.
105+
106+
- **Base URL**: `https://<your-sim-domain>/api/scim/v2`
107+
- **Token Key**: your Sim token
108+
109+
Enable group sync if you plan to map groups.
110+
111+
</Tab>
112+
</Tabs>
113+
</Step>
114+
115+
<Step>
116+
### Map your groups
117+
118+
Groups mean nothing to Sim until you say what they stand for. In **Settings → SSO → Directory provisioning → Group mappings**, point each pushed group at one or more of:
119+
120+
- a **permission group**, which governs models, integrations, and capabilities
121+
- a **workspace**, at Read, Write, or Admin
122+
- the **organization admin role**
123+
124+
A group can carry several mappings. When two groups grant the same workspace at different levels, the stronger one wins. The organization's default permission group cannot be a target: it governs by having no members.
125+
126+
Turning on **Match permission groups by name** maps a pushed group to an existing permission group of the same name automatically, and remaps it when the group is renamed. Nothing is created.
127+
128+
Mapping a permission group to a directory group switches that permission group to explicit membership permanently: it governs exactly the people in it, and an empty group governs nobody. A permission group that governed everyone in its workspaces stops doing so the moment it is mapped, so map groups you created for the directory rather than your organization-wide ones.
129+
130+
</Step>
131+
132+
</Steps>
133+
134+
## How access is withdrawn
135+
136+
Sim records every grant it makes on your behalf. When someone leaves a group, what the directory granted is taken back.
137+
138+
**Managed membership locking**, on by default, makes the directory the source of truth for provisioned members: Sim refuses invitations, workspace grants, workspace role changes, and organization role changes for them, because the next sync would revert them anyway. Access a member already held by hand when a mapping started covering it counts as directory access from then on, so it is withdrawn with the mapping. Removals stay possible so an administrator can always act in an emergency.
139+
140+
With locking off, manual access layers on top of directory access: access granted by hand stays when a group is left, and a workspace role raised by hand above what the directory set is left alone.
141+
142+
## Provisioning and SSO together
143+
144+
A member the directory created can sign in with SSO immediately; the two resolve to the same account through your verified domain.
145+
146+
If you want the directory to be the only way in, enable **Disable just-in-time provisioning** in the connection settings. Sim then refuses to create membership for someone signing in who was never provisioned.
147+
148+
## Watching a sync
149+
150+
**Settings → SSO → Directory provisioning → Activity** lists recent authenticated requests with their status and, for a failure, what was wrong. Providers report a failed cycle without saying what they sent, so this is usually the fastest way to see the cause. A request that fails to authenticate has no connection to log against, so a wrong or revoked token shows up only as your provider's own authentication error.
151+
152+
Sim also re-applies every group mapping once an hour, so drift cannot persist. You can run it on demand with **Reconcile now**, which is also how a change to the connection settings reaches members before the next sync.
153+
154+
## Reference
155+
156+
- Base URL: `https://<your-sim-domain>/api/scim/v2`
157+
- Authentication: `Authorization: Bearer <token>`
158+
- Resources: `/Users`, `/Groups`, plus `/ServiceProviderConfig`, `/ResourceTypes`, and `/Schemas`
159+
- Filters: `eq` only, up to ten terms joined with `and`. Users: `id`, `userName`, `externalId`, `emails.value` (also `emails[type eq "work"].value`), `active`. Groups: `id`, `displayName`, `externalId`
160+
- Limits: 1,500 requests per minute per connection, 1 MB per request, 5,000 members per group
161+
- `userName` is stored and returned lower-cased; top-level attributes and schema extensions Sim does not model (custom attributes included) are kept and returned as sent, and a PUT preserves ones it omits
162+
- Group display names are unique within a connection, ignoring case
163+
- Page size: up to 100 per request
164+
165+
<FAQ items={[
166+
{
167+
question: "What happens to someone's workflows when they are deactivated?",
168+
answer: "Nothing. A deactivation blocks sign-in and stops their personal API keys, and leaves every workspace, workflow, and credential they own exactly as it was. Reactivating them restores access. Only a removal — which your provider sends explicitly — reassigns what they owned. A deactivated member also keeps their seat until they are removed."
169+
},
170+
{
171+
question: "Can the directory provision someone outside our verified domains?",
172+
answer: "No. Sim refuses to create or move an account to an address whose domain your organization has not verified. This is what prevents one tenant's directory from claiming an account it does not own."
173+
},
174+
{
175+
question: "What if the email already belongs to a Sim account?",
176+
answer: "If the account is in your organization, or in none, Sim attaches to it. If it belongs to a different organization, Sim returns a conflict — a Sim account can belong to only one organization, so someone has to be removed there first."
177+
},
178+
{
179+
question: "Can the directory remove our organization owner?",
180+
answer: "No. Sim refuses, because it would leave nobody able to administer billing or transfer ownership. Transfer ownership in Sim first."
181+
},
182+
{
183+
question: "What happens when we run out of seats?",
184+
answer: "Sim refuses the new member with a message your provider shows to its administrator. Add seats in Sim and the next sync will pick them up. Team plans grow their seat count automatically; fixed-seat plans do not."
185+
},
186+
{
187+
question: "Does a deactivation sign someone out immediately?",
188+
answer: "Their sessions are deleted at once and their personal API keys stop authenticating immediately. The organization's security version changes at the same time, so a browser still holding a cached session cookie is re-checked on its next request and refused."
189+
},
190+
{
191+
question: "What happens to deactivated members if we turn provisioning off?",
192+
answer: "They stay deactivated. Disabling the connection stops the directory from making changes; it does not undo the ones it made. To restore someone without the directory, remove them from the organization and invite them again."
193+
},
194+
{
195+
question: "Can we still invite people manually?",
196+
answer: "Only with managed membership locking turned off. With it on, Sim refuses manual changes for provisioned members, because the directory would revert them on its next sync."
197+
}
198+
]} />

apps/docs/content/docs/platform/enterprise/self-hosted.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ Three features do not need a flag at all: **custom branding**, **session policie
4848
| Organizations | `ORGANIZATIONS_ENABLED` | `NEXT_PUBLIC_ORGANIZATIONS_ENABLED` |
4949
| Permission groups | `ACCESS_CONTROL_ENABLED` | `NEXT_PUBLIC_ACCESS_CONTROL_ENABLED` |
5050
| SAML and OIDC sign-in | `SSO_ENABLED` | `NEXT_PUBLIC_SSO_ENABLED` |
51+
| Directory provisioning (SCIM) | `SCIM_ENABLED` | `NEXT_PUBLIC_SCIM_ENABLED` |
5152
| Custom branding — on by default | `WHITELABELING_ENABLED` | `NEXT_PUBLIC_WHITELABELING_ENABLED` |
5253
| Audit logs | `AUDIT_LOGS_ENABLED` | `NEXT_PUBLIC_AUDIT_LOGS_ENABLED` |
5354
| Usage tracking | `USAGE_MONITORING_ENABLED` | `NEXT_PUBLIC_USAGE_MONITORING_ENABLED` |

apps/docs/content/docs/platform/enterprise/sso.mdx

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ With **Automatic** provisioning, no invitation is required for organization memb
278278
Sign-in must start from Sim. Launching from your identity provider's app portal (Microsoft's **My Apps**, Okta's dashboard tile) sends an unsolicited assertion, which Sim rejects. This is deliberate — accepting them would let anyone replay an assertion into your tenant — but it means an IdP-initiated test fails even when the configuration is correct.
279279
</Callout>
280280

281-
SSO provisioning creates internal organization members but does not grant workspace access. External workspace members are different: they are invited to a specific workspace without joining your organization or consuming one of your seats. Existing invitations and external access take precedence over automatic provisioning so their intended role and workspace grants are preserved.
281+
SSO provisioning creates internal organization members but does not grant workspace access. To grant workspace access from your identity provider, use [directory provisioning](/platform/enterprise/scim) and map a pushed group to a workspace. External workspace members are different: they are invited to a specific workspace without joining your organization or consuming one of your seats. Existing invitations and external access take precedence over automatic provisioning so their intended role and workspace grants are preserved.
282282

283283
<Callout type="info">
284284
Password-based login remains available. Forcing all organization members to use SSO exclusively is not yet supported.
@@ -305,7 +305,7 @@ SSO provisioning creates internal organization members but does not grant worksp
305305
},
306306
{
307307
question: "Does disabling someone in the identity provider remove their Sim access?",
308-
answer: "No. Disabling the IdP account blocks future SSO authentication, but Sim does not currently receive SCIM deprovisioning or IdP logout events to remove membership or revoke active Sim sessions. Remove or suspend the user in Sim as part of offboarding."
308+
answer: "With [directory provisioning](/platform/enterprise/scim) connected, yes: your identity provider sends the deactivation, and Sim blocks sign-in and stops their API keys while leaving everything they own intact. With SSO alone, disabling the IdP account only blocks future SSO authentication — remove or suspend the user in Sim as part of offboarding."
309309
},
310310
{
311311
question: "Can I still use email/password login after enabling SSO?",
@@ -342,6 +342,10 @@ Self-hosted deployments use environment variables instead of the billing/plan ch
342342
SSO_ENABLED=true
343343
NEXT_PUBLIC_SSO_ENABLED=true
344344

345+
# Optional: directory provisioning (SCIM), configured from the SSO settings page
346+
SCIM_ENABLED=true
347+
NEXT_PUBLIC_SCIM_ENABLED=true
348+
345349
# Required if you want users auto-added to your organization on first SSO sign-in
346350
ORGANIZATIONS_ENABLED=true
347351
NEXT_PUBLIC_ORGANIZATIONS_ENABLED=true

apps/docs/content/docs/platform/self-hosting/environment-variables.mdx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -251,6 +251,7 @@ Enterprise features are unlocked by configuration rather than billing on self-ho
251251
|----------|-------------|
252252
| `ENTERPRISE_ENABLED`, `NEXT_PUBLIC_ENTERPRISE_ENABLED` | Enable the whole enterprise feature set |
253253
| `SSO_ENABLED`, `NEXT_PUBLIC_SSO_ENABLED` | Enable SAML and OIDC single sign-on on its own. See [Authentication](/platform/self-hosting/authentication#sso-saml-and-oidc) |
254+
| `SCIM_ENABLED`, `NEXT_PUBLIC_SCIM_ENABLED` | Enable directory provisioning on its own. Needs SSO. See [Directory provisioning](/platform/enterprise/scim) |
254255
| `INSTANCE_ORG_NAME` | Name of the organization every user joins automatically at signup |
255256
| `INSTANCE_ORG_SLUG` | Slug for that organization (derived from the name when omitted) |
256257
| `INSTANCE_ORG_OWNER_EMAIL` | Owner of that organization (defaults to the first user to sign up) |

apps/docs/openapi-v2-billing.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -488,7 +488,8 @@
488488
"CONNECTOR_MANAGED_RESOURCE_READ_ONLY",
489489
"PERMISSION_GROUP_CAPABILITY_BLOCKED",
490490
"INTEGRATION_NOT_ALLOWED",
491-
"INSUFFICIENT_SCOPE"
491+
"INSUFFICIENT_SCOPE",
492+
"SCIM_MANAGED_MEMBERSHIP"
492493
],
493494
"title": "Forbidden detail code",
494495
"description": "Stable cause code for an actionable `403` response."

apps/docs/openapi-v2-files-audit.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3186,7 +3186,8 @@
31863186
"CONNECTOR_MANAGED_RESOURCE_READ_ONLY",
31873187
"PERMISSION_GROUP_CAPABILITY_BLOCKED",
31883188
"INTEGRATION_NOT_ALLOWED",
3189-
"INSUFFICIENT_SCOPE"
3189+
"INSUFFICIENT_SCOPE",
3190+
"SCIM_MANAGED_MEMBERSHIP"
31903191
],
31913192
"title": "Forbidden detail code",
31923193
"description": "Stable cause code for an actionable `403` response."

apps/docs/openapi-v2-knowledge.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4618,7 +4618,8 @@
46184618
"CONNECTOR_MANAGED_RESOURCE_READ_ONLY",
46194619
"PERMISSION_GROUP_CAPABILITY_BLOCKED",
46204620
"INTEGRATION_NOT_ALLOWED",
4621-
"INSUFFICIENT_SCOPE"
4621+
"INSUFFICIENT_SCOPE",
4622+
"SCIM_MANAGED_MEMBERSHIP"
46224623
],
46234624
"title": "Forbidden detail code",
46244625
"description": "Stable cause code for an actionable `403` response."

apps/docs/openapi-v2-logs.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -824,7 +824,8 @@
824824
"CONNECTOR_MANAGED_RESOURCE_READ_ONLY",
825825
"PERMISSION_GROUP_CAPABILITY_BLOCKED",
826826
"INTEGRATION_NOT_ALLOWED",
827-
"INSUFFICIENT_SCOPE"
827+
"INSUFFICIENT_SCOPE",
828+
"SCIM_MANAGED_MEMBERSHIP"
828829
],
829830
"title": "Forbidden detail code",
830831
"description": "Stable cause code for an actionable `403` response."

apps/docs/openapi-v2-resources.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5023,7 +5023,8 @@
50235023
"CONNECTOR_MANAGED_RESOURCE_READ_ONLY",
50245024
"PERMISSION_GROUP_CAPABILITY_BLOCKED",
50255025
"INTEGRATION_NOT_ALLOWED",
5026-
"INSUFFICIENT_SCOPE"
5026+
"INSUFFICIENT_SCOPE",
5027+
"SCIM_MANAGED_MEMBERSHIP"
50275028
],
50285029
"title": "Forbidden detail code",
50295030
"description": "Stable cause code for an actionable `403` response."

0 commit comments

Comments
 (0)