SCIM Provisioning & Directory Sync UI: Chat Prompts
When you sell to enterprise customers, "users sign up themselves" stops working. Their IT requires central control: when an employee joins, they get auto-provisioned in your product (with right role); when they leave the company, they're auto-deprovisioned (immediately, no manual deactivation). The standard for this is SCIM (System for Cross-domain Identity Management) — an API spec their IdP (Okta / Azure AD / Google Workspace / OneLogin / JumpCloud) calls into your product to create / update / delete users + groups.
SCIM is the foundational requirement for enterprise SaaS. Without it, enterprise IT teams won't deploy you broadly (they'd have to manage your user list manually). With it, you become enterprise-friendly.
This is distinct from SSO / Enterprise Auth (which is authentication — proving who someone is). SCIM is provisioning — managing the lifecycle of who exists in your system. Both matter; many enterprise customers demand both.
When This Belongs
Use SCIM provisioning when:
- You sell to mid-market+ / enterprise (Okta / Azure AD shops)
- Customers asking "do you support SCIM?" in security questionnaires
- Manual user management at customer side becomes pain point
- You support workspaces with 100+ users
Don't bother when:
- SMB / self-serve product
- Single-team usage
- Pre-revenue
What SCIM Does
The IdP (Okta / Azure AD / etc.) becomes the source of truth for users. When IT changes things in the IdP, it pushes to your product:
- User created: when someone joins customer's company, IdP pushes user create to your product
- User updated: name change, email change, department change, role attribute change
- User deactivated: when someone leaves the company, IdP pushes deactivation
- Group created / updated / deleted: groups in IdP map to roles or workspaces in your product
- Group membership changed: user added to / removed from group
Your product implements an HTTP endpoint that follows the SCIM 2.0 spec; IdP calls it.
Build vs Buy
For most B2B SaaS, the build/buy decision is decisive.
Buy: WorkOS Directory Sync (Default Recommendation)
WorkOS abstracts SCIM across all major IdPs. You implement a single webhook endpoint; WorkOS handles the SCIM protocol for Okta / Azure AD / Google Workspace / OneLogin / JumpCloud / etc. You receive normalized webhooks: "user created in Okta" → you create user in your DB.
Strengths:
- One integration covers 20+ IdPs
- Handles edge cases (different IdPs implement SCIM slightly differently)
- ~2-week implementation
- $$ pricing scales with customer count
Pricing: ~$125-500/month per connected customer organization (varies).
Buy: Auth0 Enterprise / Clerk Enterprise / Stytch / Frontegg
If you're already using these for SSO, they often include SCIM as part of enterprise tier.
Build: Direct SCIM Implementation
You implement SCIM 2.0 endpoints directly. Most code:
- POST /Users (create user)
- PUT /Users/:id (full update)
- PATCH /Users/:id (partial update)
- DELETE /Users/:id (deactivate)
- GET /Users (list with filters)
- POST /Groups, etc.
Strengths:
- No per-customer fee
- Maximum control
- Customizable to your data model
Weaknesses:
- 4-8 weeks of engineering work
- Each IdP has quirks (Okta vs Azure AD)
- Ongoing maintenance as IdPs update
For most B2B SaaS at $5M-50M ARR, WorkOS is the right call. Cost is small relative to enterprise deal sizes; engineering time saved is significant. Build direct only at enterprise scale ($100M+ ARR) where the per-customer fees become meaningful.
Architecture (with WorkOS)
I'm building SCIM provisioning for my B2B SaaS using WorkOS Directory Sync.
Architecture:
1. Customer admin sets up Directory Sync in WorkOS (their Okta connects to our WorkOS account)
2. WorkOS receives SCIM events from their Okta
3. WorkOS sends normalized webhooks to our endpoint
4. Our endpoint processes: create / update / deactivate users + groups in our DB
Implement:
1. WorkOS account + Directory Sync configured
2. Webhook endpoint at POST /api/webhooks/workos with HMAC verification
3. Event handlers:
- dsync.user.created → create user in our DB
- dsync.user.updated → update user fields
- dsync.user.deleted → deactivate user (don't delete; preserve audit history)
- dsync.group.created / updated / deleted → manage roles or workspaces
- dsync.group.user_added / removed → update group membership
4. Idempotency: same event delivered twice doesn't duplicate
5. Retry logic: WorkOS retries on webhook failure; we handle gracefully
Stack: Next.js + WorkOS SDK + Drizzle.
User Mapping
The trickiest part: mapping IdP attributes to your data model.
Map SCIM attributes to my user model:
Standard SCIM user attributes:
- userName (typically email)
- name.givenName, name.familyName, name.formatted
- emails (primary)
- active (boolean — true = enabled; false = deactivated)
- title
- department
- employeeNumber
- locale
- timezone
Custom enterprise attributes (per IdP):
- managerId
- costCenter
- division
- custom org-specific fields
Mapping decisions:
- Email is unique identifier (email = primary key in our user table?)
- Or use SCIM-provided externalId (more stable; emails can change)
- Department/Title → metadata or role mapping?
- Locale → user preference
Recommended:
- Store externalId (SCIM-provided UUID) as stable identity
- Store email separately; allow email to change
- Capture department / title as metadata
- Map specific groups to specific roles
Show me the user model + mapping logic.
Stack: Drizzle.
Group / Role Mapping
Map IdP groups to roles or workspaces in my product:
Common patterns:
Pattern A: Group-to-Role
- IdP has groups: "saas-admins", "saas-members", "saas-viewers"
- Map: group "saas-admins" → user role 'admin' in my workspace
- When user added to group → grant role
- When user removed → revoke role
Pattern B: Group-to-Workspace
- IdP has groups: "engineering-team", "marketing-team"
- Map: group "engineering-team" → membership in "Engineering" workspace
- Customer admin maps groups to workspaces in our admin UI
Pattern C: Hybrid
- Some groups → roles
- Some groups → workspaces
- Rest ignored
Implementation:
- Mapping table: scim_group_id → my_role | my_workspace
- Customer admin configures mapping in our UI
- On group membership change: re-evaluate user's roles + workspace memberships
UI for customer admin:
- "/admin/scim-mapping"
- List of synced groups from their IdP
- Drop-down to map each to: role / workspace / both / none
- Save + apply
Stack: Next.js + Drizzle + WorkOS API.
Customer Setup Experience
The UX for customer admins to enable SCIM.
Build the customer-facing SCIM setup flow:
1. Workspace admin goes to Settings > Security > Directory Sync
2. Click "Enable SCIM provisioning"
3. We create WorkOS Directory Sync connection; receive setup URL
4. Show step-by-step instructions:
- "1. Log into your IdP (Okta / Azure AD / etc.)"
- "2. Add our SAML/SCIM application"
- "3. Copy this URL: <SCIM endpoint>"
- "4. Copy this token: <Bearer token>"
- "5. Configure attribute mapping (we provide template)"
6. Customer admin does the work in their IdP
7. We auto-detect first sync; show confirmation: "Connected to your Okta. Synced X users."
8. Customer admin maps groups to roles / workspaces
9. Activate — new users from IdP auto-provisioned
Per-IdP guides:
- Okta-specific instructions (with screenshots)
- Azure AD specific
- Google Workspace specific
- OneLogin
- JumpCloud
- Generic SCIM (for unsupported IdPs)
Tools / docs:
- Pre-built application templates in Okta marketplace
- Screen-recorded video walkthroughs
Stack: Next.js + WorkOS + your docs.
Deactivation Behavior
The most consequential SCIM event: deactivation.
Decide what happens when SCIM marks user as inactive:
Options:
Option A: Hard deactivate immediately
- User can't log in
- Sessions revoked
- Data preserved (per retention policy)
- Most enterprise-aligned (security teams want immediate offboarding)
Option B: Soft deactivate with grace
- User flagged inactive
- Sessions revoked but data accessible by admins
- Hard delete after X days
Option C: Read-only mode
- User can log in but can't make changes
- Useful if customer wants former employee to wrap up before full deactivation
Recommended: Option A (immediate) is enterprise-standard. Option B/C if customer requests.
Implementation:
1. Receive dsync.user.deleted webhook
2. Set user.deactivated_at = NOW()
3. Revoke all sessions (delete from sessions table; invalidate JWTs)
4. Block API key usage (mark API keys associated with user as revoked)
5. Audit log entry: "User deactivated via SCIM by [IdP system]"
6. Notification to workspace admins (optional; batch daily summary)
Edge case: deactivated user is sole admin
- Block deactivation if user is sole admin (return error to SCIM)
- Force customer to designate new admin first
- Or: auto-promote earliest-tenured admin
Stack: Next.js + your auth + Drizzle.
Group Membership Changes
Handle dynamic role changes via group membership:
Scenario: User in IdP gets moved from "saas-members" to "saas-admins":
1. WorkOS receives group membership change events
2. Webhooks to our endpoint:
- dsync.group.user_removed (saas-members)
- dsync.group.user_added (saas-admins)
3. We update user role in real time
Edge cases:
- User in multiple groups mapped to different roles → take highest
- User removed from group not mapped → no-op
- User in admin group + removed → demote to default member role
- Race condition: user removed + added simultaneously → settle to final state
Audit:
- Log every role change via SCIM in audit log
- Surface to customer admin: "Role changed for X via Okta sync"
Stack: Next.js + Drizzle + audit log.
Common Pitfalls
Building SCIM directly when WorkOS would suffice. 4-8 weeks of dev for marginal benefit. Use WorkOS.
Forgetting idempotency. Same webhook delivered twice; user created twice. Use externalId as stable key.
No retry handling. Webhook fails; WorkOS retries; race condition causes inconsistent state. Plan retries.
Mapping email as primary key. Email changes (marriage, role change); breaks linkage. Use externalId.
Missing deactivation handling. Former employees still have active sessions. Revoke on deactivation.
Blocking IdP traffic with rate limits. WorkOS retries on 429s but enterprise customer's first sync might push 1000 users. Allow burst capacity.
No customer-facing setup guide. IT teams give up halfway through SCIM config. Provide per-IdP step-by-step.
Ignoring custom attributes. Customer wants department-based access; you ignored department field. Map at minimum: department + title.
Group mapping not configurable by customer. You hardcode IdP group names; doesn't fit other customer's groups. Make mapping per-customer.
Sole-admin deactivation. SCIM deactivates only admin; workspace orphaned. Block + alert.
No audit log of SCIM events. Investigation: "who got admin role last week?" — answer unclear. Log all SCIM-driven changes.
Sync delays not surfaced. New employee added in IdP; can't access product for hours. Show last-sync time + force-sync option.
No way to test before activation. Customer activates SCIM; users disappear because of mapping bug. Provide test mode.
Webhook signature verification skipped. Anyone can POST as WorkOS. Always verify HMAC.
Stale connections after IdP changes. Customer changes IdP config; sync breaks; nobody notices. Health-check + alert.
Edge cases per IdP not handled. Okta sends slightly different payload than Azure AD; one breaks. Test all major IdPs.
No bulk import for first sync. Customer activates SCIM with 5K existing users; sync takes hours; partial state. Bulk import path.
Privacy concerns ignored. Customer's IdP sends SSN as employeeNumber attribute; you store + leak. Filter sensitive attributes.
Error responses not SCIM-compliant. SCIM spec defines specific error formats; you return generic 500s; IdP confused. Follow SCIM error spec.
No way to disable SCIM mid-flight. Customer wants to pause sync while restructuring; you don't support pause. Add admin toggle.
Forgetting groups → workspace mapping. Only support groups → roles; multi-workspace customers struggle. Support both.
See Also
- SSO / Enterprise Auth — authentication counterpart
- Roles & Permissions
- Multi-Tenancy
- Sub-Account & Parent-Child Org Hierarchy
- Workspace / Tenant Switcher
- Audit Logs
- Customer-Facing Audit Logs
- Account Suspension & Fraud Holds
- Account Deletion & Data Export
- Account Merge / Org Transfer
- Two-Factor Auth
- Session Management Patterns
- Email Verification Flow
- Password Reset / Magic Link
- Social Login OAuth
- Workspace Branding / Custom Domains / White-Label
- Settings & Account Pages
- API Keys
- Customer Notes & Internal Annotations
- In-App Status Banners & System Notifications
- Localized Pricing per Region
- Plan Upgrade, Downgrade & Mid-Cycle Billing Changes
- Auth Providers (VibeReference)
- Authentication (VibeReference)
- Identity Verification & KYC Tools (VibeReference)
- HR & Payroll Tools (VibeReference)
- Compliance Automation Tools (VibeReference)
- Healthcare HIPAA-Compliant Stack & Tools (VibeReference)
- B2B Procurement Navigation (LaunchWeek)
- Enterprise POC Management (LaunchWeek)
- Trust Center / Security Page (LaunchWeek)
- Strategic Account Planning (LaunchWeek)