IDENTITY GOVERNANCE · LAB
Reconcile every account in your tenant and act on what you find
Classify your tenant's accounts as matched, orphaned, dormant, shared or service, check token activity before calling anything unused, and act in the safe order of investigate, disable, notify, wait and delete.
ReadyUses your lab tenant
The lesson
Builds on: Sources of truth.
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.
Disable the orphan before deleting anything
Recorded as
tenant.users.locksucceeded.Disable the service client nobody owns
Recorded as
tenant.oauth.clients.updatesucceeded forlab-tmp-labfeed.The disabled client can no longer get tokens
Recorded as
oauth.tokenrejected (client_disabled) forlab-tmp-labfeed.Disabling a client something depends on stops it
Recorded as
oauth.tokenrejected (client_disabled) forlab-hr-feed.
Setup
The lesson's pharmacy reconciliation found a hand-made account, a shared login and a forgotten service account. This setup plants the same three in your tenant, next to the simulated workforce and the people you already have.
Open a bash shell and set the variables and helpers from the directory lab, plus
HR_IDandHR_SECRET(read -rs).In the portal, create
[email protected](J Smith) and add it toPrint support. Do not sign in as it.Create
[email protected](Front Desk) with a password you imagine Ava and Cora sharing. In a private window, sign in as it once at$ISSUER/login.In OAuth > Clients, create a confidential client
lab-tmp-labfeedwithclient_credentialsandscim-<id6>.read, and use it once, as an old integration did:
export FEED_ID=<lab-tmp-labfeed client_id>
read -rs FEED_SECRET; export FEED_SECRET
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $(token_for "$FEED_ID" "$FEED_SECRET" "$SCIM_SCOPE.read")" "$SCIM/Users?count=1"
Press Start on the lab page.
Walkthrough
Matched versus unclaimed. Accounts carrying a source's key are matched; active accounts without one are unclaimed.
scim -G "$SCIM/Users" --data-urlencode 'filter=active eq true and externalId pr' --data-urlencode count=200 | jq .totalResults
scim -G "$SCIM/Users" --data-urlencode 'filter=active eq true and not (externalId pr)' --data-urlencode count=200 \
--data-urlencode 'attributes=userName' | jq -r '.Resources[] | [.id, .userName] | @tsv'
For each unclaimed account, search Audit for its ID and note how it was created: tenant.users.create in the portal, account.register by the person, or SCIM without an externalId.
Why it matters: this is "Finding orphans". Reconciliation starts from the accounts that exist and asks whose each one is. The unclaimed list is a list of questions, not of deletions: Ava, Ben and Cora are on it too, because they were made by hand.
Missed leavers. In the HR Simulator, open a lifecycle run's details and list the people it expected to lock or delete. Read each by
externalId. Any that is still active is a leaver the tenant still holds; any that is locked but still in groups is a leaver whose access was disabled but not removed.
Why it matters: a missed leaver is the most common orphan, like lwong in the lesson. It shows that a removal never reached this system.
Dormant accounts. On Users, scan the Last sign-in column. Choose thresholds: 45 days for anyone with a management role, 90 for everyone else.
lab-tmp-jsmith2shows "Never". Note thatmeta.lastModifiedis not usage: an HR update changes it without anyone signing in.
Why it matters: this is "Accounts nobody uses". Access nobody uses is probably access nobody needs, and nobody would notice someone else using it.
Service accounts. For each client on OAuth > Clients, find its last token request: open Audit, Source Protocol activity, and search the client ID.
lab-tmp-labfeedhas oneoauth.tokenfrom setup and no recorded owner.lab-hr-feedhas many. Neither has ever "signed in".
Why it matters: this is "Shared and service accounts". A dormancy rule based on sign-ins would flag lab-hr-feed, which the HR feed depends on. Check token activity before disabling anything as unused.
Shared accounts. Search Audit, Protocol activity, for Front Desk's ID. Its sign-in says Front Desk, never which person. In your notes, give it an owner and a plan: individual accounts for Ava and Cora, then lock it.
Why it matters: a shared login attributes every action to nobody in particular, and one person's access cannot be removed without changing it for everyone.
Act in order. Work through each finding and record every decision, including keeps, in
reconciliation.json.lab-tmp-jsmith2: matches nobody, no API activity, made by hand. Lock it now and investigate after, because locking is easy to undo.lab-tmp-labfeed: no owner, no current purpose. Disable it on OAuth > Clients, then confirm that a token request now fails.Front Desk: lock it once Ava and Cora use their own accounts.
curl -s -u "$FEED_ID:$FEED_SECRET" -d grant_type=client_credentials -d "scope=$SCIM_SCOPE.read" "$ISSUER/oauth/token" | jq .
The token request answers invalid_client, and Audit records oauth.token rejected with client_disabled. Deletion waits until the investigation is finished.
Why it matters: this is "What to do with what you find". Investigate, disable, notify, wait, then delete. Deleting first turns a cleanup into an outage and destroys history an investigation might need.
Fix the cause.
lab-tmp-jsmith2exists because accounts can be created in the portal without a source key. Write the rule you would enforce, "every new account carries anexternalIdor a register ID", and keep step 1's unclaimed query as its monthly check.
Break it
Disable a client that something depends on. Start a short HR Simulator Onboarding wave (2 people, 5 minutes), then disable
lab-hr-feedon OAuth > Clients while it runs. The run's next event cannot get a token, and the simulator stops the run with the reasontoken_denied. Audit recordsoauth.tokenrejected withclient_disabledforlab-hr-feed.
Restore: enable lab-hr-feed again and reconnect it in the HR Simulator.
Why it matters: last sign-in said nothing about this account; its token activity did.
Check your work
Press Check my progress. The checks look for, in order:
tenant.users.locksucceeded (step 6)tenant.oauth.clients.updatesucceeded forlab-tmp-labfeed(step 6)oauth.tokenrejected withclient_disabledforlab-tmp-labfeed(step 6)oauth.tokenrejected withclient_disabledforlab-hr-feed(Break it)
After Cleanup, step 1's unclaimed query returns only accounts you deliberately kept, each with a reason in reconciliation.json.
Cleanup
Delete
lab-tmp-jsmith2,lab-tmp-frontdeskand thelab-tmp-labfeedclient, andunset FEED_SECRET.Confirm
lab-hr-feedis enabled and connected.Keep
reconciliation.jsonfor the evidence lab.
Missing infrastructure
G52: last sign-in cannot be filtered or exported, clients have no owner or last-use field, and shared accounts have no owner field. With them, steps 3 to 5 would be queries instead of reading pages one by one.