Agent Wranglers

Business sign-on and provisioning Required

Remove access at once when the directory says so

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 to do it

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. 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, so a removed user loses access within that time. Reactivation through SCIM restores the membership.

Example

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));
});
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"}

How to 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.

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.