Prompt
How do I build user provisioning and deprovisioning with SSO?
Latest observation
To build user provisioning and deprovisioning with SSO, you usually combine authentication (SSO) with identity lifecycle management (provisioning/deprovisioning). SSO alone logs users in; it does not create, update, or remove accounts unless you add a provisioning standard or build your own sync.
The standard ways to do it
1) SSO + SCIM (most common)
Use:
- SSO for login via SAML or OIDC
- SCIM for automated user lifecycle management
SCIM lets the customer’s identity provider (IdP) create, update, and deactivate users in your app.
Typical flow:
- Admin assigns a user/group in Okta/Azure AD/Google Workspace
- IdP sends SCIM
POST /Usersto create the account - If role/department/name changes, IdP sends
PATCH /Users/{id} - If user leaves, IdP sends
PATCHorDELETEto deactivate/deprovision
Best for:
- Enterprise SaaS
- B2B apps
- Automated joiner/mover/leaver workflows
2) SSO + Just-in-time provisioning (JIT)
If a user signs in via SSO and does not exist yet:
- Create the account on first login
- Optionally assign default roles/teams based on SAML/OIDC claims
This is simpler than SCIM but has a limitation:
- Deprovisioning is not automatic unless you also monitor the IdP or sync periodically
Best for:
- Faster implementation
- Smaller customers
- Apps that don’t need strict deprovisioning guarantees
3) SSO + directory sync / API sync
Some customers prefer:
- LDAP/AD sync
- Scheduled imports
- Custom API-based user management
This is less standard than SCIM, but sometimes needed for legacy environments.
Recommended architecture
Auth layer
Support one or both:
- SAML 2.0
- OIDC/OAuth 2.0
Provisioning layer
Support:
- SCIM 2.0
- Optional JIT fallback if SCIM isn’t enabled
Identity model in your app
Use a stable internal user record, for example:
user.id— internal immutable IDuser.emailuser.external_identity_iduser.idp_tenant_iduser.status— active / suspended / deleteduser.rolesuser.groups
Map IdP identity to your user record using:
external_idfrom SCIM- or a combination of
issuer + subject - avoid relying only on email, because emails can change
Provisioning lifecycle design
Create
When SCIM POST /Users arrives:
- Validate auth from the IdP
- Check if user already exists
- Create local user if not present
- Store the SCIM
idas the external identity key - Set initial status = active
Update
When SCIM PATCH /Users/{id} arrives:
- Update profile fields like name, email, title, manager
- Sync role/group membership if needed
- Be careful with email changes and identity collisions
Deprovision
When the IdP removes access:
- Mark user as
inactive,suspended, ordeprovisioned - Revoke active sessions/tokens
- Remove app access
- Optionally retain data for auditing
- Decide whether to soft-delete or hard-delete later
Reactivation
If the user is reassigned:
- Re-enable the account
- Restore access/role mappings
- Issue new sessions
Important product decisions
1) Soft delete vs suspend
Usually prefer:
- Suspend/deactivate immediately
- Keep the record for audit/history
Only hard-delete if:
- Required by policy or legal request
- You can safely re-create without data loss issues
2) What happens to owned data?
When a user is deprovisioned, decide:
- Transfer ownership to manager/admin
- Leave data accessible to team
- Archive content
- Block login but preserve records
3) Role mapping
You can map:
- IdP groups → app roles
- IdP attributes → permissions
- SCIM entitlements → feature access
Keep role logic configurable per tenant if possible.
4) Session revocation
Deprovisioning should also:
- Revoke refresh tokens
- Invalidate web sessions
- Remove API keys if tied to user identity
SCIM endpoints you’ll likely need
At minimum:
GET /scim/v2/UsersPOST /scim/v2/UsersGET /scim/v2/Users/{id}PATCH /scim/v2/Users/{id}DELETE /scim/v2/Users/{id}
Often also:
GET /scim/v2/GroupsPOST /scim/v2/GroupsPATCH /scim/v2/Groups/{id}
Useful SCIM user fields:
userNamename.givenNamename.familyNameemailsactiveexternalIddisplayName
Security considerations
- Authenticate SCIM requests using bearer token, OAuth, or signed credentials
- Validate tenant isolation carefully
- Make operations idempotent
- Log provisioning changes for auditability
- Rate-limit and handle retries gracefully
- Use webhook/event processing if your IdP supports it, but don’t rely only on webhooks for correctness
A practical implementation pattern
If you’re building a SaaS app:
- Implement SSO login first
- Add JIT provisioning to reduce friction
- Add SCIM 2.0 for enterprise customers
- Support group-to-role mapping
- On deprovisioning, disable sessions immediately
This gives you:
- Easy onboarding
- Enterprise-grade lifecycle management
- Cleaner admin workflows
Common pitfalls
- Using email as the only user key
- Not revoking sessions on deactivation
- Treating SSO login as provisioning
- Not handling reassignments or duplicate accounts
- Ignoring group sync and role drift
- Failing to support idempotency in SCIM
Quick rule of thumb
If you want this to work well for enterprise customers:
- SSO = how users authenticate
- SCIM = how users are provisioned and deprovisioned
If you want, I can also provide:
- a reference architecture diagram,
- a SCIM data model/schema,
- or a step-by-step implementation plan for Okta/Azure AD.