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

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.

  1. Connect lab-hr-feed to the HR Simulator

    Recorded as tenant.hr.connect succeeded.

  2. The source creates a joiner

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

  3. Ava can no longer change her own email

    Recorded as account.profile rejected (not_permitted) about [email protected].

  4. The help desk tool overwrites a department

    Recorded as scim.user.patch succeeded for lab-provisioning.

  5. The source restores its own value

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

  6. A manager that is not a user is refused

    Recorded as scim.user.patch rejected (invalid_value) for lab-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.

  1. 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).

  2. 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.

  3. In OAuth > Clients, create a confidential client named lab-hr-feed with client_credentials and the scopes scim-<id6>.users, scim-<id6>.groups and scim-<id6>.passwords. Copy the secret once.

export HR_ID=<lab-hr-feed client_id>
read -rs HR_SECRET; export HR_SECRET
  1. Give Ava a password if she has none, so you can sign in as her in step 4.

  2. Press Start on the lab page.

  3. Open the HR Simulator, connect lab-hr-feed with its ID and secret, and read the connection report: token_granted (or token_narrowed) and scim_ready.

  4. Start an Onboarding wave: 8 people, 15 minutes, initial passwords on. Let it run while you read the first steps.

Walkthrough

  1. 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 shows scim.user.create with lab-hr-feed as actor.

Why it matters: this is "Authoritative sources". Facts start at their source and travel outwards, and the record names the source.

  1. 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.

  1. Write the ownership table. Three columns: attribute, owner, and the one supported way it changes.

AttributeOwnerHow it changes
externalId, name, title, department, manager, activeHR Simulator through lab-hr-feedThe next HR event
id, metaThe tenantNever set by a client
Password and sign-in methodsThe personAt $ISSUER/account/security
EmailHR, unless you let people change itDecide 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.

  1. 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/account and 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.

  1. 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.

  1. 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.

  1. 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.

  1. 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

  1. As lab-hr-feed, set a manager that is not a user: replace $ENT:manager with {"value":"00000000-0000-4000-8000-000000000000"}. The answer is 400 invalidValue: the manager must be the id of another user in this tenant. The directory refuses a reference it cannot follow.

  2. On OAuth > Clients, remove scim-<id6>.groups from lab-hr-feed. With the existing HR_TOKEN, PATCH any simulated department group. The answer is 403 with WWW-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.connect succeeded (Setup step 6)

  • scim.user.create succeeded by lab-hr-feed (step 1)

  • account.profile rejected with not_permitted for Ava (step 4)

  • scim.user.patch succeeded by lab-provisioning (step 5)

  • scim.user.patch succeeded by lab-hr-feed (step 6)

  • scim.user.patch rejected with invalid_value by lab-hr-feed (Break it)

In Audit, also find tenant.hr.run and tenant.authentication.update.

Cleanup

  1. Make sure the person from step 5 is back in Engineering.

  2. Keep the simulated workforce, both clients and Accept passwords over SCIM on. Later lifecycle, rules, roles and review labs use them.

  3. 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 department writable only by lab-hr-feed, so step 5 would be refused instead of needing step 6 to repair it.

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