Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

IDENTITY GOVERNANCE · LAB

End a leaver's access, including the access that is already open

Sign in as a simulated leaver, watch the source's deactivation end their tokens, separate disable, remove and delete, handle an urgent departure in order, and find what they left behind.

ReadyUses your lab tenant

The lesson

Builds on: Changing jobs.

New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.

Your progress

Press Start before you begin. Only events your tenant records after that count, in the order below. Checking reads your tenant's Audit, so you need Audit read access in it.

  1. View a leaver's generated password to sign in as them

    Recorded as tenant.hr.password.view succeeded.

  2. The source deactivates the leaver

    Recorded as scim.user.lock succeeded for lab-hr-feed.

  3. UserInfo refuses the leaver's existing token

    Recorded as oidc.userinfo rejected (invalid_token).

  4. Ben locks Cora in an urgent departure

    Recorded as tenant.users.lock succeeded about [email protected].

  5. A stale source enables Cora again

    Recorded as scim.user.unlock succeeded for lab-hr-feed about [email protected].

  6. The leaver's integration stops once its client is disabled

    Recorded as oauth.token rejected (client_disabled) for lab-tmp-export.

Setup

  1. Open a bash shell and set the variables and helpers from the directory lab, plus HR_ID and HR_SECRET (read -rs) for lab-hr-feed.

  2. Use the paused Full lifecycle run from the changing jobs lab. If it has finished, start a new one (8 people, leavers 25 percent, 30 minutes, initial passwords on) and pause it once the joiners have succeeded.

  3. If your OAuth > Flow policy allows refresh tokens, you can add offline_access in step 1 and try the refresh in step 3. Otherwise skip the refresh parts.

  4. Make sure Ben can sign in at $ISSUER/manage and holds Help desk.

  5. Press Start on the lab page.

Walkthrough

  1. The leaver's last shift. In the run plan, find a person with a "Leaver: lock (active false)" event. Use View password for that person, which the tenant records as tenant.hr.password.view. Open $ISSUER/token-decoder, sign in as them with openid profile email, and copy the access token into the shell without echoing it.

read -rs USER_AT; export USER_AT
curl -s -H "Authorization: Bearer $USER_AT" "$ISSUER/oidc/userinfo" | jq '{sub, name, email}'

UserInfo answers 200 with their sub, name and email.

Why it matters: this is "The last day". Before the contract ends, the person holds an open session and a live token, like Dr. Moreau's workstation session and mobile app.

  1. The contract ends. Advance the run to the lock event. Audit shows scim.user.patch and scim.user.lock by lab-hr-feed, sharing one correlation ID.

  2. What ended, and what did not.

    • Run the UserInfo request from step 1 again. It now answers 401 with invalid_token: the tenant revoked the person's tokens when active became false.

    • If you have a refresh token, a refresh in the Token Decoder fails with invalid_grant.

    • A new sign-in as the person fails, and Audit (Source Protocol activity) records account.sign_in rejected with account_locked.

    • Run btl-lab decode "$USER_AT" to read its aud, then btl-lab verify "$USER_AT" --issuer "$ISSUER" --audience <that aud>. The signature and exp checks still pass.

Why it matters: this is "Ending access that is already open". Disabling stops new sign-ins, and this tenant also ends what it issued. A resource server that only validates the JWT locally keeps accepting it until it expires, which is why access token lifetimes are short and why UserInfo and introspection see what local validation cannot.

  1. Disable is not remove. Read the person: scim "$SCIM/Users/<id>" | jq '{active, groups}'. active is false and the groups are still listed. Remove each membership as the provisioning service would.

jq -n --arg u "<id>" '{schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"remove",path:("members[value eq \""+$u+"\"]")}]}' \
  | scim -X PATCH "$SCIM/Groups/<group id>" --data-binary @- -o /dev/null -w '%{http_code}\n'

Why it matters: this is "Disable, remove, delete". If the account is ever enabled again, by mistake or because the person returns, it should not come back with everything it held.

  1. Delete after retention. Advance to "Leaver: delete user". Reading the ID now returns 404. Search Audit for the ID: the create, any move, the lock, your removals and the delete are all still there.

Why it matters: deleting the account does not delete the history an investigation needs.

  1. An urgent departure, in the wrong order. Cora is leaving at once. In Roles, add tenant.users.lock to Help desk. As Ben at $ISSUER/manage, lock Cora. Then play HR's next sync, which still says Cora is employed.

export HR_TOKEN=$(token_for "$HR_ID" "$HR_SECRET" "$SCIM_SCOPE.users $SCIM_SCOPE.groups")
export CORA=$(scim -G "$SCIM/Users" --data-urlencode 'filter=userName eq "[email protected]"' | jq -r '.Resources[0].id')
jq -n '{schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:"active",value:true}]}' \
  | curl -s -X PATCH "$SCIM/Users/$CORA" -H "Authorization: Bearer $HR_TOKEN" -H "Content-Type: application/scim+json" --data-binary @- | jq '{active}'

Cora is active again. Audit shows tenant.users.lock by Ben, then scim.user.unlock by lab-hr-feed.

Why it matters: this is "Urgent departures". The source must record the termination first, or its next update undoes the help desk's work.

Restore: record the departure at the source by sending the same PATCH with "value": false. Cora stays locked until Cleanup.

  1. What the person leaves behind. Suppose Cora had set up a weekly export. Create a confidential client lab-tmp-export with client_credentials and only scim-<id6>.read, then get a token with it and list users.

export EXPORT_ID=<lab-tmp-export client_id>
read -rs EXPORT_SECRET; export EXPORT_SECRET
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $(token_for "$EXPORT_ID" "$EXPORT_SECRET" "$SCIM_SCOPE.read")" "$SCIM/Users?count=1"

It answers 200 while Cora is locked, because a client is not a person. Now disable lab-tmp-export on OAuth > Clients and request a token again: 401 invalid_client, and Audit records oauth.token rejected with client_disabled.

Why it matters: integrations need an owner who is not the leaver. Nothing in the tenant records who owns this client, so the leaver process could not have listed it.

Break it

  1. Step 6 is the break: a source that has not changed re-enables a leaver. Its Restore locks Cora through the source.

Check your work

Press Check my progress. The checks look for, in order:

  • tenant.hr.password.view succeeded (step 1)

  • scim.user.lock succeeded by lab-hr-feed (step 2)

  • oidc.userinfo rejected with invalid_token (step 3)

  • tenant.users.lock succeeded for Cora (step 6)

  • scim.user.unlock succeeded by lab-hr-feed for Cora (step 6)

  • oauth.token rejected with client_disabled for lab-tmp-export (step 7)

Cleanup

  1. Delete lab-tmp-export on OAuth > Clients, and unset EXPORT_SECRET USER_AT.

  2. Unlock Cora in the portal. She is part of the lab cast. Previously revoked tokens stay revoked.

  3. Keep tenant.users.lock in Help desk; the separation of duties lab uses it.

  4. Let the lifecycle run finish.

Missing infrastructure

  • G30: there is no admin view that lists one user's open sessions and ends a chosen one. This lab does not need it, because locking ends sessions and tokens, but an incident responder would.

  • G52: clients have no owner field, so step 7's export could not be found from Cora's record before she left.

Back to all labs

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

The Lab