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

OAUTH 2.0 · LAB

Ask for one album instead of the whole photo library

Request read access to album 42 with authorization_details and compare the consent screen and token with photos.read. Today, see the parameter ignored and feel the scope-string workaround.

PlannedUses your lab tenant

The lesson

New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.

Planned. The core of this lab waits on platform features that are not built yet. The planned walkthrough shows exactly how it will run; Do today is a real exercise you can do now.

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

These steps are real today.

  1. In Lab Photos, confirm lab-printer (confidential web, redirect URI http://127.0.0.1:8765/callback, PKCE required, scope photos.read) and that OAuth > Flow policy allows authorization_code.

  2. Store its credentials and the album request you will send:

export CLIENT_ID='<lab-printer client id>'; read -rs CLIENT_SECRET; export CLIENT_SECRET
AD='[{"type":"photo_album","actions":["read"],"identifier":"42"}]'
enc() { jq -rn --arg v "$1" '$v|@uri'; }

Planned walkthrough

This walkthrough runs once your tenant supports Rich Authorization Requests with tenant-defined types (G15) and hosts a sample photo API (G3).

Planned setup

  1. Open OAuth > Authorization details (planned) and create the type photo_album. Fields: actions (list, allowed value read), identifier (string, required), datatypes (list, allowed images and captions), locations (list, allowed $ISSUER/resource). Consent text: "Read album {identifier}". Allowed clients: lab-printer. Audit records tenant.oauth.authorization_details.create (planned event name).

Steps

  1. The server lists the types it understands:

GET$ISSUER/.well-known/oauth-authorization-server Open in console
GET $ISSUER/.well-known/oauth-authorization-server

authorization_details_types_supported is ["photo_album"].

Why it matters: a client confirms the server knows a type before building an object of it.

  1. The scope baseline. Start btl-lab callback, then request photos.read:

eval "$(btl-lab pkce)"; eval "$(btl-lab state)"
echo "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http%3A%2F%2F127.0.0.1%3A8765%2Fcallback&scope=photos.read&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256"

The consent screen says the printer wants to view your photos, and the token's scope is photos.read.

Why it matters: the scope cannot say which album, so the token can read every photo in Ava's library.

  1. One album. Run the listener again, and send the details instead of a scope:

eval "$(btl-lab pkce)"; eval "$(btl-lab state)"
echo "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http%3A%2F%2F127.0.0.1%3A8765%2Fcallback&authorization_details=$(enc "$AD")&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256"

The consent screen reads "Read album 42". Approve as Ava, check state and iss, then exchange the code:

read -rs CODE
RESP=$(curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/token" -d grant_type=authorization_code --data-urlencode "code=$CODE" \
  --data-urlencode redirect_uri=http://127.0.0.1:8765/callback --data-urlencode "code_verifier=$VERIFIER")
jq .authorization_details <<<"$RESP"; TOKEN=$(jq -r .access_token <<<"$RESP"); unset CODE RESP
btl-lab decode "$TOKEN"

The token response repeats the approved object, and the token carries an authorization_details claim and no scope.

Why it matters: access described as data travels from client to consent screen to token, with the part that changes every time, the album, as a value rather than a private scope format.

  1. Scopes and details together. Repeat step 3 with scope=openid added. One consent screen shows both the sign-in and the album.

Why it matters: the server must process both forms together and show the combined request, never two separate approvals.

  1. Enforcement at the sample photo API:

curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" "$ISSUER/resource/albums/42/photos"
curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $TOKEN" "$ISSUER/resource/albums/57/photos"

The first is 200, the second 403.

Why it matters: details narrow what a token allows, and the API enforces them on each request while keeping its own checks, such as whether album 42 is Ava's.

Do today

  1. Send the album request to today's tenant. Run step 3 with &scope=photos.read added to the URL (your tenant needs a scope to issue a token), approve and exchange the code:

jq 'has("authorization_details")' <<<"$RESP"

Run this before unset RESP. It prints false, and the consent screen showed only photos.read. Your tenant ignores authorization_details like any parameter it does not define. The client must notice that no details were granted and must not behave as if the token were limited to album 42: it can read the whole library.

  1. Feel the scope-string workaround. In OAuth > Scopes, create the common scope lab-tmp-album-42.read and assign it to lab-printer. Request it instead of photos.read. It works, but every album needs another scope, and the consent screen can only show that scope's fixed description. Imagine a second album, a different action, or the lesson's 42.50 EUR payment squeezed into a string such as pay:42.50:EUR, where a parsing mistake costs someone money.

  1. A server must not advertise what it lacks. In OAuth > Metadata, add the custom member {"authorization_details_types_supported": ["photo_album"]} and save. The change is refused, and Audit records tenant.oauth.metadata.update rejected.

  1. Nothing else may fake it either. In OAuth > Access token managers, try to add a claim mapping named authorization_details to any manager. The name is reserved, so the change is refused: only the authorization server, once it supports RAR, may set that claim from what Ava approved.

Break it

  1. Planned: remove lab-printer from the photo_album type's allowed clients and repeat step 3. The listener receives error=invalid_authorization_details with state and iss, and Audit records oauth.authorize rejected invalid_authorization_details (planned reason). Add the client back.

Why it matters: supporting a type is not the same as allowing every client to use it.

Check your work

There are no automated checks while this lab is planned. For Do today, look in Lab Photos' Audit for oauth.authorize with code_issued and oauth.token succeeded for lab-printer, tenant.oauth.scopes.create, and the rejected tenant.oauth.metadata.update and manager update.

Cleanup

Delete the scope lab-tmp-album-42.read and run unset TOKEN.

Missing infrastructure

  • G15 Rich Authorization Requests: parse and validate authorization_details at the authorization and token endpoints against tenant-defined types (fields, kinds, allowed values, required fields, allowed clients), render the consent text, store the approval with the grant, and return it in the token response, the JWT claim, introspection and authorization_details_types_supported.

  • G3 Sample protected resource API: a hosted photo API with per-user albums that reads the claim. Until then, btl-lab resource on loopback can show the album check once tokens carry details.

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