IDENTITY GOVERNANCE · LAB
Bring a joiner in disabled, link her history, and let her activate on day one
Find Mia's earlier internship record by a stable ID, link her new hire to it, give her day-one access while the account stays disabled, enable it on the start date and let Mia set up her own sign-in.
ReadyUses your lab tenant
The lesson
Builds on: What a directory holds.
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.
Link the new hire to her existing record
Recorded as
scim.user.patchsucceeded forlab-provisioning.Give the disabled account its team group
Recorded as
scim.group.patchsucceeded forlab-provisioning.A sign-in before the start date is refused
Recorded as
account.sign_inrejected (account_locked).Enable the account on the start date
Recorded as
scim.user.unlocksucceeded forlab-provisioning.Mia sets her own password from her mailbox
Recorded as
account.reset_passwordsucceeded (reset_completed).Mia signs in for the first time
Recorded as
account.sign_insucceeded (signed_in).
Setup
Mia Lane is joining Lab Photos as a print technician. She is the lab's person with a real inbox, so her activation email reaches you. In 2024 she spent a summer as an intern in print support, recorded under the register ID C1874. HR now sends this hire record:
{
"type": "hire",
"employeeNumber": "E10482",
"legalName": { "given": "Mia", "family": "Lane" },
"jobTitle": "Print technician",
"department": "Print support",
"manager": "Cora Diaz",
"startDate": "<a date next week>",
"previousRegisterId": "C1874"
}
Open a bash shell and set the variables and helpers from the directory lab, plus Mia's address:
export [email protected](your own plus-address).On Users, check whether a user with
$MIA_EMAILalready exists from another track. If one does, delete it: this lab needs her 2024 record to be the only one.Create Mia's 2024 internship record, inactive, as the history the hire should find.
jq -n --arg ent "$ENT" --arg email "$MIA_EMAIL" '{schemas:["urn:ietf:params:scim:schemas:core:2.0:User",$ent],
externalId:"C1874", userName:"mia.lane", name:{givenName:"Mia",familyName:"Lane"},
emails:[{value:$email,type:"work",primary:true}], active:false, ($ent):{department:"Print support",title:"Summer intern"}}' \
| scim -X POST "$SCIM/Users" --data-binary @- | jq '{id, externalId, active}'
export CORA=$(scim -G "$SCIM/Users" --data-urlencode 'filter=userName eq "[email protected]"' | jq -r '.Resources[0].id')
Press Start on the lab page.
Walkthrough
Look before creating. Search by the new employee number, then by the earlier register ID, then by name alone.
for f in 'externalId eq "E10482"' 'externalId eq "C1874"' 'name.familyName eq "Lane"'; do
scim -G "$SCIM/Users" --data-urlencode "filter=$f" | jq -c --arg f "$f" '{filter:$f, found:.totalResults}'; done
The answers are 0, 1, and 1 or more.
Why it matters: this is "Creating the identity". The register ID is a stable identifier both records hold, strong enough to link. A name match is only a candidate for a person to check, and creating a second record would split Mia's history in two.
Link the hire to the existing record. Keep
activefalse.
export MIA=$(scim -G "$SCIM/Users" --data-urlencode 'filter=externalId eq "C1874"' | jq -r '.Resources[0].id')
jq -n --arg ent "$ENT" --arg cora "$CORA" '{schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[
{op:"replace",path:"externalId",value:"E10482"}, {op:"replace",path:"title",value:"Print technician"},
{op:"replace",path:($ent+":employeeNumber"),value:"E10482"}, {op:"replace",path:($ent+":manager"),value:{value:$cora}}]}' \
| scim -X PATCH "$SCIM/Users/$MIA" --data-binary @- | jq '{id, externalId, active}'
Same id, new externalId, active: false. Search Audit for $MIA: the 2024 create and today's link sit under one subject.
Why it matters: linking brings back history, not access. Nothing from the internship grants anything to the new job.
Day-one access while disabled. Add Mia to
Print support, the team group her job brings.
export PRINT=$(scim -G "$SCIM/Groups" --data-urlencode 'filter=displayName eq "Print support"' | jq -r '.Resources[0].id')
jq -n --arg u "$MIA" '{schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"members",value:[{value:$u}]}]}' \
| scim -X PATCH "$SCIM/Groups/$PRINT" --data-binary @- -o /dev/null -w '%{http_code}\n'
Why it matters: this is "Access on day one". The account and its access exist a week early so slower systems can catch up, and nobody can use them yet.
Prove the account is unusable. In a private window, go to
$ISSUER/loginand try$MIA_EMAILwith any password. Sign-in fails, and Audit (Source Protocol activity) showsaccount.sign_inrejected withaccount_locked.
Why it matters: an account that works before day one is access without a reason, and nobody would notice it being used.
The start date arrives. Enable the account with a correlation ID you can search for.
export CID=$(uuidgen | tr A-Z a-z)
jq -n '{schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:"active",value:true}]}' \
| scim -X PATCH "$SCIM/Users/$MIA" -H "X-Correlation-ID: $CID" --data-binary @- | jq '{id, active}'
Search Audit for $CID: scim.user.patch and scim.user.unlock share it.
Why it matters: the enable is a recorded change with its own trigger, not a side effect, so it can be explained later.
Activation instead of a temporary password. In the private window, open
$ISSUER/reset-password, enter$MIA_EMAIL, open the email and set a password. The tenant's password rules and breached-password check apply. Then sign in at$ISSUER/loginand, if your authentication policy offers TOTP or a passkey, enroll one at$ISSUER/account/security. Audit recordsaccount.reset_passwordwithreset_completed, thenaccount.sign_inwithsigned_in.
Why it matters: this is "The first sign-in". Whoever completes it controls the account. Here that is whoever controls Mia's mailbox, and no password ever travelled through a manager or a ticket. Compare Accept passwords over SCIM, which lets a provisioning client set a password that someone then has to deliver.
A cancelled hire. Create a second joiner who never starts, then remove the account.
jq -n '{schemas:["urn:ietf:params:scim:schemas:core:2.0:User"], externalId:"E10511", userName:"lab-tmp-eli",
name:{givenName:"Eli",familyName:"Varga"}, emails:[{value:"[email protected]",type:"work",primary:true}], active:false}' \
| scim -X POST "$SCIM/Users" --data-binary @- | jq -r .id
Delete it with scim -X DELETE "$SCIM/Users/<eli id>" -o /dev/null -w '%{http_code}\n' (204), then read it (404). Audit keeps scim.user.create and scim.user.delete for that ID.
Why it matters: this is "When the start date moves". A delayed start needs no change while active stays false. An account that was never enabled is removed, and the history of the cancelled hire stays.
Catch a no-show. List recent joiners that are enabled.
scim -G "$SCIM/Users" --data-urlencode "filter=active eq true and meta.created gt \"$(date -u -d '7 days ago' +%Y-%m-%dT00:00:00Z)\"" \
--data-urlencode 'attributes=userName,displayName' | jq -r '.Resources[].userName'
On Users, check each one's Last sign-in. "Never" a week after the start date is a dormant account from its first day, to report to the manager. On macOS, use date -u -v-7d +%Y-%m-%dT00:00:00Z instead.
Why it matters: the lesson's safeguard for a no-show is a report on enabled accounts nobody has used.
Break it
Create Mia again with a new
externalIdbut the same email, as a process that skipped step 1 would. The answer is409withscimType: uniqueness. This is the duplicate-identity mistake being refused by the tenant's Refuse the new user setting; the fix is the search in step 1, not a different address.
Check your work
Press Check my progress. The checks look for, in order:
scim.user.patchsucceeded bylab-provisioning(step 2)scim.group.patchsucceeded bylab-provisioning(step 3)account.sign_inrejected withaccount_locked(step 4)scim.user.unlocksucceeded bylab-provisioning(step 5)account.reset_passwordsucceeded withreset_completed(step 6)account.sign_insucceeded withsigned_in(step 6)
Cleanup
Keep Mia as an active member of
Print support. She is part of the lab cast.Confirm that
lab-tmp-eliis deleted.
Missing infrastructure
G51: the tenant has no start date that enables an account by itself, and no one-time activation invitation that expires at the end of the start date. With them, steps 5 and 6 would not depend on you acting at the right moment, and an unused activation could not wait for someone else to find it.