IDENTITY GOVERNANCE · LAB
Make the HR Simulator the source of truth, then watch a copy disagree
Let a simulated HR feed publish a workforce, write an attribute ownership table, close a second path for email, override a department as the help desk and restore the owner's value.
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.
Connect lab-hr-feed to the HR Simulator
Recorded as
tenant.hr.connectsucceeded.The source creates a joiner
Recorded as
scim.user.createsucceeded forlab-hr-feed.Ava can no longer change her own email
Recorded as
account.profilerejected (not_permitted) about[email protected].The help desk tool overwrites a department
Recorded as
scim.user.patchsucceeded forlab-provisioning.The source restores its own value
Recorded as
scim.user.patchsucceeded forlab-hr-feed.A manager that is not a user is refused
Recorded as
scim.user.patchrejected (invalid_value) forlab-hr-feed.
Setup
The HR Simulator at https://beyondthelogin.dev/hr/ plays the HR system: it publishes fictional joiners, movers and leavers into your tenant over SCIM with its own client. In this lab lab-provisioning plays the help desk's tool.
Open a bash shell and set the variables and helpers from the directory lab (
ISSUER,SCIM,SCIM_SCOPE,ENT,CLIENT_ID,CLIENT_SECRET,token_for,TOKEN,scim).In User Management > Provisioning, turn on Accept passwords over SCIM and save. The simulator's generated passwords let you sign in as simulated people in later labs.
In OAuth > Clients, create a confidential client named
lab-hr-feedwithclient_credentialsand the scopesscim-<id6>.users,scim-<id6>.groupsandscim-<id6>.passwords. Copy the secret once.
export HR_ID=<lab-hr-feed client_id>
read -rs HR_SECRET; export HR_SECRET
Give Ava a password if she has none, so you can sign in as her in step 4.
Press Start on the lab page.
Open the HR Simulator, connect
lab-hr-feedwith its ID and secret, and read the connection report:token_granted(ortoken_narrowed) andscim_ready.Start an Onboarding wave: 8 people, 15 minutes, initial passwords on. Let it run while you read the first steps.
Walkthrough
Watch the source publish. The run timeline shows "Create department group", then "Joiner: create user" for each department's manager first, then the other joiners and "Add to group". Open one joiner event: it lists HTTP status
201, a Correlation ID, the server request ID and the SCIM id, with a link to Audit. Follow the link. Audit showsscim.user.createwithlab-hr-feedas actor.
Why it matters: this is "Authoritative sources". Facts start at their source and travel outwards, and the record names the source.
Read what HR owns. Once the run has completed:
scim -G "$SCIM/Users" --data-urlencode 'filter=externalId sw "HRSIM-"' \
--data-urlencode "attributes=externalId,title,$ENT:department,$ENT:manager" | jq '.Resources'
Each simulated person has an externalId, a title, an enterprise department and a manager.value, which is another user's id.
Why it matters: this is "Who knows best?". These attributes have a natural owner, and it is not the directory.
Write the ownership table. Three columns: attribute, owner, and the one supported way it changes.
| Attribute | Owner | How it changes |
|---|---|---|
| externalId, name, title, department, manager, active | HR Simulator through lab-hr-feed | The next HR event |
| id, meta | The tenant | Never set by a client |
| Password and sign-in methods | The person | At $ISSUER/account/security |
| HR, unless you let people change it | Decide in step 4 |
Why it matters: this is "Owning an attribute". The table tells integration builders which way each value flows, and tells everyone else where to get something fixed.
Close a second path for email. In Authentication policy, open the profile settings and turn off the option that lets tenant users change their own email. Save. In a private window, sign in as Ava at
$ISSUER/accountand try to change her email. The page answers "Your administrator manages your email address."
Why it matters: a person editing a value that HR also sends creates two owners for one attribute. The setting enforces your table at runtime instead of on paper.
The help desk overrides a department. Pick one simulated person in Engineering and change their department as
lab-provisioning, the help desk's tool, with a correlation ID you can search for.
export P=<id of one simulated Engineering person>
jq -n --arg ent "$ENT" '{schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:($ent+":department"),value:"Support"}]}' \
| scim -X PATCH "$SCIM/Users/$P" -H "X-Correlation-ID: $(uuidgen | tr A-Z a-z)" --data-binary @- | jq "{id, dept: .\"$ENT\".department}"
The answer is 200 with department Support.
Why it matters: like the lesson's help desk, the edit may even be right about the facts. It is wrong about the path: the change has no record at the source, and the source will send its own value again.
The nightly read restores the owner's value. Play HR's next sync with the source's own client.
export HR_TOKEN=$(token_for "$HR_ID" "$HR_SECRET" "$SCIM_SCOPE.users $SCIM_SCOPE.groups")
jq -n --arg ent "$ENT" '{schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:($ent+":department"),value:"Engineering"}]}' \
| curl -s -X PATCH "$SCIM/Users/$P" -H "Authorization: Bearer $HR_TOKEN" -H "Content-Type: application/scim+json" --data-binary @- | jq "{id, dept: .\"$ENT\".department}"
Search Audit for the person's ID. Two scim.user.patch events by different clients each list the attribute they changed, by name only, never by value.
Why it matters: this is "When sources disagree". Precedence follows ownership, not recency. The record shows that a source and a copy disagreed, which is often the first sign that a source is late.
Missing data. Find active people with no manager.
scim -G "$SCIM/Users" --data-urlencode "filter=active eq true and not ($ENT:manager pr)" \
--data-urlencode 'attributes=displayName,externalId' | jq -r '.Resources[] | [.displayName, (.externalId // "(none)")] | @tsv'
The department managers and your portal users (Ava, Ben, Cora) come back.
Why it matters: a person with no manager has nobody to approve their requests or review their access. The department managers are expected; the portal users are a data gap.
Values nobody expected. As the source, make the kind of typing mistake the lesson warns about, then catch it.
jq -n --arg ent "$ENT" '{schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"replace",path:($ent+":department"),value:"Finanse"}]}' \
| curl -s -X PATCH "$SCIM/Users/$P" -H "Authorization: Bearer $HR_TOKEN" -H "Content-Type: application/scim+json" --data-binary @- > /dev/null
scim -G "$SCIM/Users" --data-urlencode 'count=200' --data-urlencode "attributes=$ENT:department" \
| jq -r ".Resources[].\"$ENT\".department // \"(none)\"" | sort | uniq -c
Finanse appears once, next to the departments that really exist. Fix it at the source: run the step 6 command again.
Why it matters: this is "Bad data, wrong access". The directory accepted a valid string. A rule that grants access by department would act on it, so a value that has never existed before is the moment to hold a change and ask its owner, not to guess a mapping.
Break it
As
lab-hr-feed, set a manager that is not a user: replace$ENT:managerwith{"value":"00000000-0000-4000-8000-000000000000"}. The answer is400 invalidValue: the manager must be the id of another user in this tenant. The directory refuses a reference it cannot follow.On OAuth > Clients, remove
scim-<id6>.groupsfromlab-hr-feed. With the existingHR_TOKEN, PATCH any simulated department group. The answer is403withWWW-Authenticate: Bearer realm="SCIM", error="insufficient_scope", because the tenant checks the scopes the client holds now, not only those inside the token.
Restore: assign scim-<id6>.groups to lab-hr-feed again and get a new HR_TOKEN.
Check your work
Press Check my progress. The checks look for, in order:
tenant.hr.connectsucceeded (Setup step 6)scim.user.createsucceeded bylab-hr-feed(step 1)account.profilerejected withnot_permittedfor Ava (step 4)scim.user.patchsucceeded bylab-provisioning(step 5)scim.user.patchsucceeded bylab-hr-feed(step 6)scim.user.patchrejected withinvalid_valuebylab-hr-feed(Break it)
In Audit, also find tenant.hr.run and tenant.authentication.update.
Cleanup
Make sure the person from step 5 is back in
Engineering.Keep the simulated workforce, both clients and Accept passwords over SCIM on. Later lifecycle, rules, roles and review labs use them.
Keep self-service email change off unless you want it back; your ownership table says HR owns email.
Missing infrastructure
G52: any client with the users scope can write any user attribute. Per-client attribute ownership would let you make
departmentwritable only bylab-hr-feed, so step 5 would be refused instead of needing step 6 to repair it.