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.
Sign in to start this lab and check your progress. Log in or create an account.
View a leaver's generated password to sign in as them
Recorded as
tenant.hr.password.viewsucceeded.The source deactivates the leaver
Recorded as
scim.user.locksucceeded forlab-hr-feed.UserInfo refuses the leaver's existing token
Recorded as
oidc.userinforejected (invalid_token).Ben locks Cora in an urgent departure
Recorded as
tenant.users.locksucceeded about[email protected].A stale source enables Cora again
Recorded as
scim.user.unlocksucceeded forlab-hr-feedabout[email protected].The leaver's integration stops once its client is disabled
Recorded as
oauth.tokenrejected (client_disabled) forlab-tmp-export.
Setup
Open a bash shell and set the variables and helpers from the directory lab, plus
HR_IDandHR_SECRET(read -rs) forlab-hr-feed.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.
If your OAuth > Flow policy allows refresh tokens, you can add
offline_accessin step 1 and try the refresh in step 3. Otherwise skip the refresh parts.Make sure Ben can sign in at
$ISSUER/manageand holdsHelp desk.Press Start on the lab page.
Walkthrough
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 withopenid 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.
The contract ends. Advance the run to the lock event. Audit shows
scim.user.patchandscim.user.lockbylab-hr-feed, sharing one correlation ID.What ended, and what did not.
Run the UserInfo request from step 1 again. It now answers
401withinvalid_token: the tenant revoked the person's tokens whenactivebecame 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_inrejected withaccount_locked.Run
btl-lab decode "$USER_AT"to read itsaud, thenbtl-lab verify "$USER_AT" --issuer "$ISSUER" --audience <that aud>. The signature andexpchecks 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.
Disable is not remove. Read the person:
scim "$SCIM/Users/<id>" | jq '{active, groups}'.activeis 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.
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.
An urgent departure, in the wrong order. Cora is leaving at once. In Roles, add
tenant.users.locktoHelp 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.
What the person leaves behind. Suppose Cora had set up a weekly export. Create a confidential client
lab-tmp-exportwithclient_credentialsand onlyscim-<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
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.viewsucceeded (step 1)scim.user.locksucceeded bylab-hr-feed(step 2)oidc.userinforejected withinvalid_token(step 3)tenant.users.locksucceeded for Cora (step 6)scim.user.unlocksucceeded bylab-hr-feedfor Cora (step 6)oauth.tokenrejected withclient_disabledforlab-tmp-export(step 7)
Cleanup
Delete
lab-tmp-exporton OAuth > Clients, andunset EXPORT_SECRET USER_AT.Unlock Cora in the portal. She is part of the lab cast. Previously revoked tokens stay revoked.
Keep
tenant.users.lockinHelp desk; the separation of duties lab uses it.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.