AUTHORIZATION AND POLICY · LAB
Object and field checks in the tenant's own APIs
See one permission give different answers for different users, a field that needs a stronger permission than its record, and lists that stay inside their scope.
Partly readyUses your lab tenant
The lesson
Builds on: Designing permissions.
New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.
Partly ready. Most of this lab runs today. Steps that wait on platform features are marked, and Missing infrastructure says what they need.
- G3 Sample protected resource API; no RFC 9728 protected resource metadata
- G22 End-user authorization engine (PDP, RBAC/ABAC/ReBAC for application data)
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.
Give Cora the Auditor role
Recorded as
tenant.users.management_roles.assignsucceeded about[email protected].Ben edits a user who holds no roles
Recorded as
tenant.users.updatesucceeded about[email protected].Ben tries to edit a role holder
Recorded as
tenant.users.updaterejected about[email protected].Ben tries to move Ava's email address
Recorded as
tenant.users.updaterejected about[email protected].
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
Nested photos, moves, signed thumbnail links and search counts need the photo library API (G3, G22). The tenant's user directory makes the same kind of per-object and per-field decisions today, through the portal and through SCIM.
Complete Read and use the tenant permission catalog first.
In Roles, add
tenant.users.updatetoHelp desk. CreateAuditorif it does not exist, withtenant.overview.read,tenant.audit.read,tenant.logs.readandtenant.roles.read.Open Users > Cora > Management roles and assign
Auditor. Cora now holds a permission Ben lacks.In Provisioning, turn SCIM on. In the SCIM console at /docs/scim/, create
lab-scim-readerwith only the read scopescim-<id6>.readandlab-provisioningwith the full scopescim-<id6>, if they do not exist. The<id6>part is the first six characters of your tenant ID.Note Ava's and Cora's user IDs from Users.
Walkthrough
In Ben's window open
$ISSUER/manage, then Users > Ava, and change her last name toArcher-Lab. It saves. Change it back.
Why it matters: Ben's permission applies to an object he may act on. The check is made against this user, not just against the Users page.
As Ben, try the same edit on Cora. It is refused with "You do not have permission to make this change."
Why it matters: the decision depends on the object. Changing someone who holds management roles requires holding every permission their roles grant, so Ben cannot weaken an Auditor's account even though he may edit users in general.
As Ben, try to change Ava's email address to
[email protected]. It is refused, while her name change went through.
Why it matters: the email field receives password resets, so moving it needs tenant.users.credentials.set as well. This is the lesson's photo.edit_rights on the same record as the caption: one route, one record, different permissions per field.
Responses built from what may be read. Get a read token and ask SCIM for two fields of Ava's record:
read -rs CLIENT_SECRET # lab-scim-reader's secret
SCIM=$(curl -s -u "<lab-scim-reader client ID>:$CLIENT_SECRET" -d grant_type=client_credentials -d "scope=scim-<id6>.read" "$ISSUER/oauth/token" | jq -r .access_token)
GET$ISSUER/scim/v2/Users/<Ava's
Open in console
GET $ISSUER/scim/v2/Users/<Ava's user ID>?attributes=userName,active HTTP/1.1
Authorization: Bearer $SCIMOnly those fields come back, with id and schemas. No request returns a password.
Why it matters: the response is built from an allowlist of readable fields. A write-only field never leaves, whatever the caller asks for, as the legal hold note stays out of every response that should not carry it.
Lists stay inside the caller's scope. Ask for a page of one user:
GET$ISSUER/scim/v2/Users?count=1
Open in console
GET $ISSUER/scim/v2/Users?count=1 HTTP/1.1
Authorization: Bearer $SCIMtotalResults counts this tenant's users only. Optional: send the same request with the same token to $ISSUER2. It is refused with 401, because the token means nothing to Lab Mail.
Why it matters: totals and pages come from the caller's filtered set. Another tenant's users do not exist for this caller, so no count can reveal them.
The same object rule through another entry point. Get a token for
lab-provisioningwith the scopescim-<id6>intoSCIM, as in step 4, and try to deactivate Cora:
PATCH$ISSUER/scim/v2/Users/<Cora's
Open in console
PATCH $ISSUER/scim/v2/Users/<Cora's user ID> HTTP/1.1
Authorization: Bearer $SCIM
Content-Type: application/scim+json
{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"replace","path":"active","value":false}]}It is refused with 403 and scimType protected_user.
Why it matters: the object rule holds on every route, not only behind the portal button. A provisioning client cannot change a person who holds management roles.
Break it
A batch with per-item results. Send one SCIM
/Bulkrequest aslab-provisioningwith two PATCH operations: set Ava'stitletoPhotographer, and set Cora'sactivetofalse.
POST$ISSUER/scim/v2/Bulk
Open in console
POST $ISSUER/scim/v2/Bulk HTTP/1.1
Authorization: Bearer $SCIM
Content-Type: application/scim+json
{"schemas":["urn:ietf:params:scim:api:messages:2.0:BulkRequest"],"Operations":[{"method":"PATCH","path":"/Users/<Ava's user ID>","data":{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"replace","path":"title","value":"Photographer"}]}},{"method":"PATCH","path":"/Users/<Cora's user ID>","data":{"schemas":["urn:ietf:params:scim:api:messages:2.0:PatchOp"],"Operations":[{"op":"replace","path":"active","value":false}]}}]}The response lists a result per operation: Ava's change succeeds and Cora's fails with the same protected_user error as step 6.
Why it matters: a batch needs a decision per item, never one decision for the first item or for the request as a whole, as the lesson's batch approval shows.
Check your work
Press Check my progress. The checks follow Cora's Auditor assignment, Ben's allowed edit, his refused edit of a role holder and his refused email change.
Also confirm by hand:
The SCIM read returned only the fields you asked for.
Step 6 and the second Bulk operation were refused with
protected_user.
Cleanup
Remove Ava's
titleif you want her record back to the start.Keep
Auditoron Cora, the extendedHelp deskon Ben, and both SCIM clients.
Missing infrastructure
G3 and G22, the photo library lab API. With seeded albums 4410 and 5230 and photos 9001 and 7310, the full lab would request
/albums/4410/photos/7310as Ava in the role of Lena and get 404, not 200; move photo 9001 to album 5230 as Ben in the role of Omar and be refused at the destination; get a short-lived signed thumbnail link only afterphoto.view; run a search whose total and filter counts match the filtered set; and send aPATCHwithuploaded_byandstatusto seefield_not_editable.