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

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.

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. Give Help desk exactly four permissions

    Recorded as tenant.roles.update succeeded.

  2. Ben's audit read is refused

    Recorded as tenant.directory.audit.list rejected.

  3. Get a photos.read token for Ava through lab-printer-app

    Recorded as oauth.token succeeded for lab-printer-app about [email protected].

  4. Try to save a role with a misspelled permission

    Recorded as tenant.roles.create rejected.

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.

  1. Complete Freshness, step-up for role holders, and SSO across two applications first. Ben then holds Help desk and has an authenticator app, which role holders need for their second step.

  2. If lab-printer-app does not exist, create it in OAuth > Clients with the single-page application preset: public, PKCE required, redirect URI http://127.0.0.1:8765/callback.

  3. 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.assign and the rest.

Walkthrough

  1. 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.update and tenant.users.credentials.set, tenant.audit.read and tenant.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.

  1. Edit Help desk so it holds exactly tenant.overview.read, tenant.users.read, tenant.users.unlock and tenant.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.

  1. In Ben's private window open $ISSUER/manage and 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.

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

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

  1. Scopes against permissions. In OAuth > Scopes, confirm photos.read exists as a common scope. Get a token for Ava through lab-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

  1. An unknown permission name. In the portal open DevTools on the Roles page and create a role lab-tmp-typo with tenant.users.unlock. In the Network panel, copy the roles/create request as fetch and paste it into the console. Before sending it, change the name to lab-tmp-typo-2, the permission to tenant.users.unlok, and command_id to a fresh value from crypto.randomUUID(). It is refused with invalid_request, and Audit records tenant.roles.create rejected.

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.

  1. 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 reason not_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 permissions list matched the four names you granted.

Cleanup

  • Delete the lab-tmp-typo role you created in Break it.

  • Keep Help desk as edited, Ben's assignment and lab-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.assign and the rest) and a decision endpoint would let the full lab ask can(user, 'photo.approve', photo) for Ava and Ben, then break it with the misspelled photo.aprove and with a decision store that times out. Both would be refused, recorded with the reasons unknown_permission and decision_failed, which is the lesson's fail-closed shape.

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