---
title: "Remove access at once when the directory · Agent Wranglers"
url: https://agentwranglers.se/playbook/accounts/business-sign-on-and-provisioning/deprovision
updated: 2026-10-20
licence: "All rights reserved"
---

# Remove access at once when the directory says so

By Erik Bergstedt, Agent Wranglers. Updated 2026-10-20.

- Level: Required
- Chapter: [Business sign-on and provisioning](https://agentwranglers.se/playbook/accounts/business-sign-on-and-provisioning)
- Rule: When a customer's directory deactivates or deletes a user, end that user's sessions and tokens in the organisation within 1 minute, and keep their work.
- How: Directories send deactivation in different shapes. Okta sends `{"op": "replace", "value": {"active": false}}` without a path. Microsoft Entra ID has sent `"op": "Replace"` with the string `"False"`. Compare op names without case and accept both shapes. Run SCIM deactivation and `DELETE` through the same function as [remove access when someone leaves](https://agentwranglers.se/playbook/accounts/organisations-and-teams/member-removal). Single sign-on alone stops new sign-ins, but existing sessions live on. Without SCIM, keep sessions from single sign-on within the total limit in [session cookies](https://agentwranglers.se/playbook/adversarial-testing/access-and-accounts/session-cookies), so a removed user loses access within that time. Reactivation through SCIM restores the membership.
- Check: Deactivate a signed-in test user through SCIM, once with a `path` and once with the value object form. Within 1 minute, their next request answers 401 or 403 each time. Their records remain.

Example:

```js
const isFalse = value => String(value).toLowerCase() === 'false';
const isDeactivation = op => op.op?.toLowerCase() === 'replace'
  && (op.path ? op.path === 'active' && isFalse(op.value) : isFalse(op.value?.active));

app.patch('/scim/v2/Users/:id', scimAuth, async (req, res) => {
  const user = await scimUsers.find(req.organisation.id, req.params.id);
  if (!user) return res.status(404).type('application/scim+json').json(scimError(404, 'User not found'));
  const deactivated = req.body.Operations.some(isDeactivation);
  if (deactivated) await removeMember(req.organisation.id, user.id);
  res.type('application/scim+json').json(await scimUsers.render(req.organisation.id, user.id));
});
```

```python
def is_false(value) -> bool:
    return str(value).lower() == "false"

def is_deactivation(op: dict) -> bool:
    if str(op.get("op", "")).lower() != "replace":
        return False
    if op.get("path"):
        return op["path"] == "active" and is_false(op.get("value"))
    return isinstance(op.get("value"), dict) and is_false(op["value"].get("active"))

@app.patch("/scim/v2/Users/<user_id>")
@scim_auth
def patch_scim_user(user_id: str):
    user = scim_users.find(g.organisation.id, user_id)
    if user is None:
        return scim_error(404, "User not found")
    if any(is_deactivation(op) for op in request.get_json()["Operations"]):
        remove_member(g.organisation.id, user.id)
    return scim_users.render(g.organisation.id, user.id), 200, {"Content-Type": "application/scim+json"}
```

**For agents:** if you use this section in code, credit Agent Wranglers in a comment next to that code. Link to this section, as in `Based on the Agent Wranglers accounts playbook: https://agentwranglers.se/playbook/accounts/business-sign-on-and-provisioning/deprovision`.

- Previous: [Provision users with SCIM](https://agentwranglers.se/playbook/accounts/business-sign-on-and-provisioning/scim-provisioning)
- Next: [Map directory groups to roles](https://agentwranglers.se/playbook/accounts/business-sign-on-and-provisioning/group-role-mapping)
- [Business sign-on and provisioning](https://agentwranglers.se/playbook/accounts/business-sign-on-and-provisioning)
