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.