Your Celery beat runs every four hours. A user is terminated in Okta at 9:03am. Your next sync fires at noon. For those three hours, a valid session token, a live API key, or an active OAuth grant is still resolving to an authorized identity in your system. Your /loginRequired decorator doesn't know the directory says that user no longer exists.
That window isn't a theoretical risk. It's a privilege escalation vector sitting in your access control layer right now — and it's the predictable output of how most Python-based SaaS backends implement SCIM deprovisioning.
Why Polling-Based SCIM Deprovisioning Has a Race Condition Baked In
The standard implementation pattern looks like this: a /Users POST handler for provisioning, a Celery task that syncs user state on a schedule, maybe a /Users PATCH for attribute updates. Deprovisioning is either a soft delete triggered by a scheduled job polling the IdP, or a webhook handler that queues a revocation event with no delivery guarantees.
The problem isn't that polling is unreliable — it's that the local users table is being treated as the source of truth. When that's the model, the identity state in your system is always a sync cycle behind the IdP. The question is just how long that gap is: four hours with a Celery beat, 24 hours with a daily sync, or 30 minutes if you've configured AD Connect. In all three cases, a terminated user has a window of continued access.
Webhooks help but don't fully close it. A webhook fires on the deactivation event, but if the handler queues the revocation asynchronously rather than executing it synchronously before returning 200, the delivery guarantee is only as strong as your queue. If that queue backs up, fails silently, or retries with backoff, the downstream revocation is delayed by an undefined amount — and the session remains valid in the interim.
Group sync compounds the exposure. Enterprises don't assign access user-by-user; they manage it through directory groups mapped to roles. A SCIM implementation that handles User resources but ignores Group membership deltas leaves a user removed from engineering-prod-access in Entra ID still carrying that role in your system until the next full reconciliation sync. That's not a UX gap — it's a privilege escalation vector that lives in your RBAC layer between sync cycles.
The Correct Architecture: IdP as Source of Truth, Synchronous Downstream Revocation
The architectural inversion that actually closes the race condition is straightforward to describe and difficult to implement at scale: the IdP is the source of truth, not the local users table. A PATCH or DELETE event from the SCIM controller should synchronously invalidate sessions, rotate or revoke tokens, and reflect group membership changes into the RBAC layer before the HTTP response returns 200.
In practice, that means a DELETE event from Okta or Entra ID can't just enqueue a revocation job. It has to immediately terminate active sessions, expire the credential, strip the role assignments, and revoke any OAuth grants — all within the same request lifecycle. The downstream app has to reflect the IdP state before the event handler returns.
Most SaaS backends can't do this because they weren't built with this model in mind. The local user record was always the live state. The IdP was the sync target. Inverting that relationship requires changes to session management, token validation, and RBAC resolution that touch almost every authenticated code path in the application.
How Zluri's Architecture Handles SCIM DELETE and Group Deltas
Zluri is built on the inverted model: the IdP or HRMS is the authoritative source of truth, and every access decision downstream is driven by the state of that source rather than a local cache.
On a deactivation event, Zluri receives the webhook from the IdP and uses a brief buffer to batch concurrent events before triggering an enrichment API call back to the source to fetch the full verified context. The full pipeline — from receiving the IdP webhook to firing downstream revocation APIs — runs in roughly 10 to 12 minutes under a 30-minute processing SLA in production. That's not synchronous HTTP-response-level revocation, and it's worth being direct about that distinction: Zluri does not hold the HTTP response open while executing downstream revocations. What it does do is bypass the standard 24-hour polling cycle entirely and execute hard revocations at the session level rather than soft deletes against a local database.
When the offboarding playbook fires, the actions are concrete. Depending on the downstream application's API capabilities, Zluri executes: explicit session invalidation across web and device sessions, credential expiry to kill the active password, and role and license stripping from the user profile. These are direct API calls to the downstream application, not state changes in a local table that will eventually propagate.
Group delta handling works through dedicated automation triggers. When a user is removed from a directory group in Entra ID or Okta, Zluri detects the membership change and executes group-based playbooks that run targeted API actions — Remove User from Group, Remove User From All Groups, Modify Group Membership — directly in the downstream SaaS applications. The role reflected in your RBAC layer is revoked dynamically on the group event rather than waiting for a full reconciliation cycle. The engineering-prod-access scenario the original question describes — where a user retains the role in the downstream app until the next full sync — is specifically what this architecture is designed to prevent.
What "Deep Deprovisioning" Actually Requires
The reason most SCIM implementations get this wrong is that deprovisioning looks simple on the provisioning side of the spec and reveals its complexity only under security scrutiny. POST a user, they exist. DELETE a user, they're gone. The spec doesn't tell you what "gone" means for active sessions, cached tokens, OAuth grants, group-derived roles, or API keys that were issued to the identity.
A minimal SCIM DELETE handler that soft-deletes the local user record and returns 200 is spec-compliant. It is also insufficient for any enterprise security requirement. The gap between spec compliance and actual access revocation is where the race condition lives.
Deep deprovisioning closes that gap by treating the DELETE event as the authoritative signal to execute revocation everywhere the identity has footprint — not just in the directory, but in every downstream system the user touched. That requires knowing where the user has active access (a discovery problem), having integrations into each of those systems (an engineering problem), and executing the revocations faster than a malicious actor can exploit the window (a latency and reliability problem).
An IGA orchestration layer like Zluri addresses all three: continuous application discovery surfaces the full access footprint, maintained API connectors handle the downstream execution, and webhook-triggered playbooks compress the revocation window from hours to minutes.
Frequently Asked Questions
What is a SCIM deprovisioning race condition?
A SCIM deprovisioning race condition occurs when there is a gap between a user being terminated in an IdP and that termination being reflected in downstream applications. If your SCIM implementation relies on periodic polling rather than real-time event handling, a terminated user can retain valid sessions, tokens, or role assignments in your system for the duration of the sync interval — creating an access window that is a privilege escalation risk.
What should a SCIM DELETE event handler do for enterprise security?
A SCIM DELETE event should synchronously invalidate all active sessions, expire credentials, revoke OAuth grants, and strip role assignments before the response returns. Most polling-based or async queue-based implementations don't meet this bar — they soft-delete the local user record and rely on a subsequent sync to propagate the state downstream. The correct model treats the IdP as the source of truth and executes revocations at the session level on the event, not on the next sync cycle.
Why does SCIM group sync matter for RBAC security?
Enterprises assign access through directory groups mapped to application roles. If your SCIM implementation handles User resources but ignores Group membership deltas, a user removed from a directory group retains the corresponding role in your downstream application until the next full reconciliation sync. Depending on your sync frequency, that's a window of minutes to hours where the user still carries elevated permissions they should no longer have.
How does an IGA platform improve on native SCIM deprovisioning?
An IGA platform like Zluri adds an orchestration layer on top of native SCIM that handles the gaps: real-time webhook processing that bypasses polling cycles, hard session-level revocations rather than soft database deletes, group membership delta triggers that update RBAC in downstream apps immediately, and audit logging across every revocation action. The result is a revocation pipeline measured in minutes rather than sync intervals.
See How Zluri's Offboarding Playbooks Handle Session Revocation and Group Deltas
If your current SCIM implementation relies on scheduled polling or async queues for deprovisioning, the race condition window is real. See how Zluri's offboarding playbooks handle session revocation and group deltas across your specific app stack — not a generic walkthrough, a review of where your current architecture leaves access open.












