AUTHORIZATION AND POLICY · LAB
Read and use the tenant permission catalog
Read your tenant's management permission catalog, grant Ben exactly four actions, and watch unknown names and missing grants fail closed.
Partly readyUses your lab tenant
The lesson
Builds on: Authentication policy and SSO.
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 Help desk exactly four permissions
Recorded as
tenant.roles.updatesucceeded.Ben's audit read is refused
Recorded as
tenant.directory.audit.listrejected.Get a photos.read token for Ava through lab-printer-app
Recorded as
oauth.tokensucceeded forlab-printer-appabout[email protected].Try to save a role with a misspelled permission
Recorded as
tenant.roles.createrejected.
Setup
The Gazette's photo permissions need an application that checks them, which is not built yet (G3, G22). Your tenant's own management API is a real permission system with the same design, so this lab practices on it.
Complete Freshness, step-up for role holders, and SSO across two applications first. Ben then holds
Help deskand has an authenticator app, which role holders need for their second step.If
lab-printer-appdoes not exist, create it in OAuth > Clients with the single-page application preset: public, PKCE required, redirect URIhttp://127.0.0.1:8765/callback.Open Roles and open the editor for
Help desk. Read the catalog it offers, grouped by area:tenant.users.read,tenant.users.lock,tenant.users.unlock,tenant.users.credentials.set,tenant.audit.read,tenant.logs.read,tenant.roles.assignand the rest.
Walkthrough
Sort part of the catalog into a short table with the columns resource, action and risk. Note pairs the tenant keeps apart on purpose:
tenant.users.updateandtenant.users.credentials.set,tenant.audit.readandtenant.logs.read.
Why it matters: each permission names an action on a resource type, like photo.upload. Editing a user's name and changing where their password resets go are decided separately, so they are separate permissions, which is the lesson's granularity test.
Edit
Help deskso it holds exactlytenant.overview.read,tenant.users.read,tenant.users.unlockandtenant.roles.read, and save.
Why it matters: Ben gets what the job needs and nothing more, as Lena got upload and caption for the festival album and nothing else.
In Ben's private window open
$ISSUER/manageand sign in with his password and authenticator app. Open the browser's DevTools console on that page and ask the service for Ben's current grants:
(await (await fetch('/api/auth/tenant/roles')).json()).permissions
The list holds the four permissions.
Why it matters: the service decides from the permissions Ben's current assignments grant, not from the role's name. Renaming Help desk would change nothing; editing its permissions changes everything.
As Ben, open the Audit page. It is refused. In the console, ask the server directly:
(await fetch('/api/auth/tenant/users/audit')).status
The answer is 403.
Why it matters: nothing grants tenant.audit.read to Ben, so the answer stays at no. A missing menu item is not the protection; the server's check is.
As yourself in the portal, open Audit and filter on outcome
rejected. Ben's refused audit read is there with the operation he attempted.
Why it matters: a refusal is evidence too. It shows who tried what, which a single admin flag could never tell you.
Scopes against permissions. In OAuth > Scopes, confirm
photos.readexists as a common scope. Get a token for Ava throughlab-printer-app: in your shell run the commands below, open the printed URL in Ava's window, approve, and paste the code.
export CLIENT_ID="<lab-printer-app client ID>"
eval "$(btl-lab pkce)"; eval "$(btl-lab state)"
echo "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://127.0.0.1:8765/callback&scope=openid%20photos.read&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256"
btl-lab callback
read -rsp "Code: " CODE; echo
curl -s "$ISSUER/oauth/token" -d grant_type=authorization_code -d "code=$CODE" -d redirect_uri=http://127.0.0.1:8765/callback \
-d "client_id=$CLIENT_ID" -d "code_verifier=$VERIFIER" | jq '{scope, error}'
The token carries photos.read. Ava still has no management permission: $ISSUER/manage sends her to /account.
Why it matters: a scope limits what a client may do on Ava's behalf; a permission says what a person may do. The lesson's printer request must pass both, and neither stands in for the other.
Break it
An unknown permission name. In the portal open DevTools on the Roles page and create a role
lab-tmp-typowithtenant.users.unlock. In the Network panel, copy theroles/createrequest as fetch and paste it into the console. Before sending it, change the name tolab-tmp-typo-2, the permission totenant.users.unlok, andcommand_idto a fresh value fromcrypto.randomUUID(). It is refused withinvalid_request, and Audit recordstenant.roles.createrejected.
Why it matters: only catalog names are accepted. A typo fails loudly on the first try instead of creating a role that silently grants nothing, or worse, a check that grants everything.
Default deny for a person with no role. In Ava's window open
$ISSUER/manage. She is sent to/account, and Logs record the request with reasonnot_permitted.
Why it matters: nothing grants Ava anything in management, so she starts and stays at no.
Check your work
Press Check my progress. The checks follow the narrowed role, Ben's refused audit read, the refused misspelled role and Ava's delegated token.
Also confirm by hand:
Ben's
permissionslist matched the four names you granted.
Cleanup
Delete the
lab-tmp-typorole you created in Break it.Keep
Help deskas edited, Ben's assignment andlab-printer-app.
Missing infrastructure
G3 and G22, the photo library lab API. A per-tenant photo API with its own permission catalog (
photo.view,photo.download,photo.upload,photo.approve,role.assignand the rest) and a decision endpoint would let the full lab askcan(user, 'photo.approve', photo)for Ava and Ben, then break it with the misspelledphoto.aproveand with a decision store that times out. Both would be refused, recorded with the reasonsunknown_permissionanddecision_failed, which is the lesson's fail-closed shape.