Backend
SCIM Deprovisioning in Multi-Tenant SaaS: What Should Actually Be Deleted?
Michay Kalika Dev.to (EN Zone)
1 views
An employee leaves Acme, so Acme’s identity provider sends your SaaS application a SCIM deprovisioning request. The same user is still an administrator in Beta. Your application should remove the access controlled by Acme and leave Beta alone.
In multi-tenant SaaS, SCIM deprovisioning should normally disable or remove the membership controlled by the authenticated tenant, not the global user identity. Delete the global identity only when no other memberships remain and your retention policy permits it.
The bug usually starts in the data model. One database row has gradually come to represent a person, a login identity, an organization membership, a set of roles, and perhaps active sessions. A handler receives DELETE /Users/{id}, deletes that row, and discovers too late that it also removed the user from another customer’s account.
I work at Frontegg, where we build customer identity infrastructure for multi-tenant applications. The design model below is general; the final section identifies Frontegg-specific behavior.
SCIM does not define your deletion boundary
SCIM standardizes how identity data moves between a client, usually an enterprise identity provider, and a service provider such as a SaaS application. It defines resources, HTTP operations, schemas, filtering, patching, and error responses.
Your internal identity model still determines what the requested resource represents and which records the operation may change.
RFC 7644 explicitly leaves the multi-tenancy scheme to the service provider. A SCIM client may control one tenant, several tenants, or a private subset of resources. The service provider must determine which tenant the authenticated connection is allowed to change.
For a multi-tenant application, deprovisioning can refer to three separate operations:
Disable a user’s access to one tenant.
Remove the membership connecting a user to that tenant.
Delete the user’s global identity from the application.
A SCIM DELETE /Users/{id} request deletes a SCIM resource according to the protocol. Your application still has to decide what that resource represents. In a multi-tenant system, it should not automatically mean “delete this person everywhere.”
Model identity and membership separately
A useful B2B identity model has at least three concepts:
User identity
├── Acme membership
│ ├── Admin role
│ └── Acme status
└── Beta membership
├── Viewer role
└── Beta status
The identity represents the person and their login. Each membership connects that identity to an organization and carries the access granted there. The SCIM connection defines which organization the directory is authorized to administer.
Keeping these separate gives the deprovisioning handler a safe target. Acme’s directory can remove or disable the Acme membership without touching the Beta membership.
A safe deprovisioning flow
Assume Alex belongs to both Acme and Beta. Acme’s directory sends a request that sets Alex’s SCIM active attribute to false.
Here is the order I would use when reviewing the handler.
1. Resolve tenant context from the connection
Bind each SCIM credential or connection to an internal tenant when the connection is created. Do not trust a tenant ID supplied in the request body as proof of authority.
The authenticated connection should resolve to Acme before the handler looks up Alex.
SCIM credential -> connection -> Acme tenant
If a connection can administer multiple tenants, require an explicit tenant selector and verify that the connection is authorized for it. RFC 7644 calls out this access-control requirement for cross-tenant use cases.
2. Resolve the tenant-scoped resource
Look up the SCIM resource in Acme’s namespace. Do not perform an unconstrained global lookup followed by a global delete.
Identifiers require care here. SCIM requires the service provider to interpret externalId within the provisioning domain. The provisioning client controls its uniqueness; the service provider does not enforce global uniqueness. Two directories can legitimately send the same externalId for different tenant contexts.
A safer lookup resembles:
SELECT membership_id, user_id
FROM memberships
WHERE tenant_id = :authenticated_tenant
AND scim_external_id = :external_id;
The tenant restriction belongs in the lookup itself, not in a later check someone might forget.
3. Disable access before considering deletion
Map active: false to a reversible access state unless your documented contract says otherwise. Disable the tenant membership, remove tenant-scoped role assignments as appropriate, and prevent new sessions for that tenant.
Do not assume that deactivation requires immediate physical deletion. Security offboarding and data-retention deletion are different workflows.
4. Revoke the right session scope
If your architecture supports tenant-scoped sessions, invalidate or prevent refresh of the affected tenant context. Do not revoke access to unrelated tenants unless your security model explicitly requires a global response.
Your behavior here depends on token lifetime, refresh-token design, session storage, and revocation capabilities. Document the delay between the directory event and effective access removal, then test it.
5. Delete the global identity only when it is orphaned
After removing the Acme membership, check whether the identity still has another valid membership.
memberships remain -> keep identity
no memberships -> apply orphan policy
“Apply the policy” is deliberate. Your application may need to retain an identity record for audit, legal, fraud-prevention, or recovery purposes even after the last membership is removed. Hard deletion should follow an approved retention process, not happen as an accidental side effect of a tenant offboarding call.
6. Record enough context to investigate the event
An audit record should identify:
The SCIM connection and tenant
The external and internal resource identifiers
The requested operation
The membership affected
Whether the global identity was retained, disabled, or queued for deletion
The result and correlation ID
Avoid copying unnecessary directory attributes or bearer tokens into logs.
What should happen in common cases?
Incoming event
Tenant-scoped action
Global identity action
active: false from Acme
Disable Alex’s Acme membership
Keep it if Alex belongs elsewhere
Remove Alex from an Acme group
Update Acme group membership and mapped access
None
DELETE from Acme
Remove or tombstone the Acme-controlled SCIM resource according to the documented contract
Evaluate only after checking other memberships and retention rules
Acme disconnects SCIM
Stop synchronization and follow the agreed tenant-offboarding policy
Never delete unrelated memberships
Delayed duplicate deprovisioning event
Return the same safe final state
Do not turn a retry into a wider deletion
Failure cases worth testing
The successful offboarding path is not enough. Test these cases before calling the integration complete:
Alex belongs to Acme and Beta; Acme deactivates Alex.
Two tenants use the same externalId value.
A deprovisioning event arrives twice.
Deactivation arrives before an earlier profile update.
A retry occurs after the membership changed but before the response was delivered.
A group removal and user deactivation arrive in the same bulk request.
The connection is valid but attempts to address another tenant’s resource.
Alex has an active session for the removed tenant and another for a tenant that should remain available.
The last membership is removed, but retention policy forbids immediate hard deletion.
For every test, assert both sides of the boundary: the targeted tenant changed, and every unrelated tenant remained unchanged.
How Frontegg handles the boundary
In Frontegg’s multi-tenant identity model, a user can have memberships in multiple accounts. Deprovisioning the user from Tenant A removes that tenant membership without changing memberships in other tenants. The global user identity is deleted only when Tenant A was the user’s final membership.
Frontegg also provides tenant-scoped SCIM provisioning and separates management operations from tenant self-service operations. Your application remains responsible for enforcing tenant context in its own resources, data stores, caches, queues, search indexes, and background jobs.
For the full protocol overview, request examples, SCIM-versus-JIT comparison, and implementation safeguards, see What Is SCIM? System for Cross-Domain Identity Management Explained. You can also inspect the Frontegg SCIM Provisioning API and the underlying SCIM 2.0 protocol.
Before shipping your own implementation, write down one sentence that defines what /Users/{id} means in your tenant model. If the answer can accidentally include another customer’s access, the deletion boundary is still too broad.
Read original: https://dev.to/ciam_michay/scim-deprovisioning-in-multi-tenant-saas-what-should-actually-be-deleted-3pih
← Previous
Why AI Test Automation Matters More as Your SaaS Codebase Grows
Next →
Java 27 in 10 Minutes — What Actually Changed
Related
SENTINEL-RL for SOCs: Architectural Gains and Cost Realities from Decoupling Semantic and Topological Reasoning
Backend
1
DEV Community
Day 15: Visualizing Embeddings with t-SNE and PCA
Backend
0
DEV Community
Magento 2 RabbitMQ Performance: Tuning Consumers and the Broker for High-Volume Stores
Backend
0
DEV Community
Caso de éxito: los toolkits gráficos
Backend
0
Dev.to (EN Zone)
Comments0
No comments yet — be the first