IDENTITY GOVERNANCE · LAB
Read your tenant's directory the way an application does
Turn on SCIM, read Ava's entry and the groups with a client credentials token, and watch her ID and sign-in subject survive a rename while a cached copy goes stale.
ReadyUses your lab tenant
The lesson
Builds on: Why access needs governance.
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.
Ask the directory what it supports, as lab-provisioning
Recorded as
scim.discovery.readsucceeded forlab-provisioning.Read Ava's entry by its ID
Recorded as
scim.user.readsucceeded forlab-provisioning.Rename Ava in the portal
Recorded as
tenant.users.updatesucceeded.Try to change membership from the user side
Recorded as
scim.user.patchrejected (mutability) forlab-provisioning.Try to put a group inside a group
Recorded as
scim.group.createrejected (invalid_value) forlab-provisioning.
Request console
Requests in this lab can be sent from this page to your tenant: open one and choose Send. Fill in the values below first. They stay in this page's memory and are gone when you leave; secrets are never stored or sent anywhere except the request you send.
Setup
From here on, the tenant's SCIM API is how your scripts read and change the directory, the way an application or a provisioning service would.
In OAuth > Flow policy, allow the
client_credentialsgrant for the tenant.In User Management > Provisioning, turn on Turn on the SCIM API for this tenant. Leave Accept passwords over SCIM off and When a new user's email matches an existing user on Refuse the new user. Save, then note the SCIM base URL and the full scope name
scim-<id6>.In OAuth > Clients, create a confidential client named
lab-provisioningwith the Machine-to-machine preset, allowclient_credentials, and assign the scopescim-<id6>. Copy the secret once. Later governance labs reuse this client.Give Ava a password with Set password on Users, and keep it in your password manager.
In a bash shell, set the variables and two small helpers. Later governance labs repeat this block.
export ISSUER=https://tenant-<id>.beyondthelogin.dev # from the tenant Overview
export SCIM="$ISSUER/scim/v2" SCIM_SCOPE=scim-<id6>
export ENT=urn:ietf:params:scim:schemas:extension:enterprise:2.0:User
export CLIENT_ID=<lab-provisioning client_id>
read -rs CLIENT_SECRET; export CLIENT_SECRET
token_for() { curl -s -u "$1:$2" -d grant_type=client_credentials -d "scope=${3:-$SCIM_SCOPE}" "$ISSUER/oauth/token" | jq -r .access_token; }
export TOKEN=$(token_for "$CLIENT_ID" "$CLIENT_SECRET")
scim() { curl -s -H "Authorization: Bearer $TOKEN" -H "Content-Type: application/scim+json" "$@"; }
token_for <client id> <secret> [scopes] asks for a client credentials token, by default with the full SCIM scope. Run the TOKEN= line again whenever a request returns 401.
Press Start on the lab page.
Walkthrough
Confirm the directory answers. Send this request, or run
scim "$SCIM/ServiceProviderConfig" | jq '{patch,bulk,filter,etag}'.
GET$ISSUER/scim/v2/ServiceProviderConfig
Open in console
GET $ISSUER/scim/v2/ServiceProviderConfig HTTP/1.1
Authorization: Bearer $TOKEN
Accept: application/scim+jsonThe answer says patch, bulk, filter (maxResults: 200) and etag are supported.
Why it matters: this is "A shared record". Applications reach a directory through a protocol, and this is the one your tenant speaks.
Note Ava's sign-in subject. Open
$ISSUER/token-decoder, run the sign-in with scopeopenid profile email, sign in as Ava and note the ID token'ssub.Read Ava's entry.
scim -G "$SCIM/Users" --data-urlencode 'filter=userName eq "[email protected]"' | jq '.Resources[0]'
export AVA=$(scim -G "$SCIM/Users" --data-urlencode 'filter=userName eq "[email protected]"' | jq -r '.Resources[0].id')
export E1=$(scim -i "$SCIM/Users/$AVA" | awk 'tolower($1)=="etag:" {print $2}' | tr -d '\r'); echo "$E1"
The entry holds id (a UUID equal to the sub from step 2), userName, name, emails, active: true, a read-only groups list with display names, and meta with created, lastModified and version. The ETag header, now in E1, matches meta.version.
Why it matters: this is "Entries and attributes". The entry is a set of named attributes, some single-valued and some lists, and only id is guaranteed never to change.
Ask whether a cached copy is current.
scim -o /dev/null -w '%{http_code}\n' -H "If-None-Match: $E1" "$SCIM/Users/$AVA"prints304.
Why it matters: an application holding a copy can check freshness cheaply instead of trusting yesterday's data.
Ava changes surname. In the portal, edit Ava: last name Brooks, email
[email protected]. Repeat step 4 with the oldE1: now200. Read Ava again: new ETag, newuserNameand email, sameid. Changing the email also ended Ava's sessions and revoked her tokens, so the Token Decoder's UserInfo call now fails.
Why it matters: this is "Identifiers that do not change". Three attributes changed and the identifier did not. An application keyed on email would now create an empty profile for Ava.
Sign in as Ava again in the Token Decoder with
[email protected]. Thesubis the same as in step 2.
Why it matters: a relying party that keys on sub follows the person through the rename, as patient records did for Dana in the lesson.
Groups are lists of members. Read the group, then try to change membership from the user side.
scim -G "$SCIM/Groups" --data-urlencode 'filter=displayName eq "Print support"' | jq '.Resources[0] | {id, members}'
export MOD=$(scim -G "$SCIM/Groups" --data-urlencode 'filter=displayName eq "Photo moderation"' | jq -r '.Resources[0].id')
jq -n --arg g "$MOD" '{schemas:["urn:ietf:params:scim:api:messages:2.0:PatchOp"],Operations:[{op:"add",path:"groups",value:[{value:$g}]}]}' \
| scim -X PATCH "$SCIM/Users/$AVA" --data-binary @- | jq .
The group lists value (user IDs) and display. The PATCH returns 400 with scimType: mutability: groups is read-only, so membership changes through the Groups endpoint.
Why it matters: this is "Groups". Membership is stored on the group, and the user's groups attribute is a reflection of it.
Nesting is refused. Try to create a group that contains
Photo moderation.
jq -n --arg g "$MOD" '{schemas:["urn:ietf:params:scim:schemas:core:2.0:Group"],displayName:"lab-tmp-all-staff",members:[{value:$g,type:"Group"}]}' \
| scim -X POST "$SCIM/Groups" --data-binary @- | jq .
The answer is 400 invalidValue: groups cannot contain other groups.
Why it matters: this directory avoids the lesson's "Students on placement inside All nursing staff" problem entirely. Many directories allow nesting, and their applications disagree about expanding it.
How an application checks membership. In the Token Decoder, look at Ava's ID token and UserInfo: there is no groups claim. An application that needs to know whether Ava is in
Print supportmust ask the directory with its own credential, as in step 7, or keep a copy that can go stale, as in steps 4 and 5.
Why it matters: this is "How applications use a directory". Every copy and cache is a place where a change has not arrived yet.
Break it
Call the API with no token:
curl -s -o /dev/null -w '%{http_code}\n' "$SCIM/Users"prints401, and theWWW-Authenticateheader saysBearer realm="SCIM".Read an ID that does not exist:
scim "$SCIM/Users/00000000-0000-4000-8000-000000000000"returns404with "User not found."
Check your work
Press Check my progress. The checks look for, in order:
scim.discovery.readsucceeded forlab-provisioning(step 1)scim.user.readsucceeded forlab-provisioning(step 3)tenant.users.updatesucceeded (step 5)scim.user.patchrejected withmutability(step 7)scim.group.createrejected withinvalid_value(step 8)
Also compare your notes: the same sub in both Token Decoder sign-ins, and different ETags before and after the rename.
Cleanup
Edit Ava back to last name Archer and email
[email protected].Keep the SCIM API on,
lab-provisioning, and your shell block. Every later governance lab uses them.