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

Share, link, and revoke in the right order

Planned: share an album with a person, a group, a link and a guest, and remove access without stale copies. Today: treat a token as a share link and watch revocation reach some checks but not others.

PlannedUses your lab tenant

The lesson

Builds on: Writing rules with attributes.

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.

Setup

Sharing photos with people, groups, links and guests needs the photo API and a relationship store (G3, G22). Bearer credentials, delegated grants, expiry and revocation that lags behind a local copy are all real today.

  1. Complete Permit and forbid rules in a token policy for lab-printer-app and the authorize and redeem helpers.

  2. In OAuth > Flow policy, allow the refresh token grant, and allow it on lab-printer-app.

  3. In OAuth > Access token managers, create lab-tmp-short: JWT format, the default signing key, access token lifetime 5 minutes, refresh token reuse grace 0 seconds. Assign it to lab-printer-app only.

  4. If lab-photo-api does not exist, create it in OAuth > Clients: confidential, no grants, with the resource server flag on. If lab-collage does not exist, create it as in Freshness, step-up for role holders, and SSO across two applications.

Planned walkthrough

These steps will run once the photo library API and its relationship store exist, with Cora as Maya, the album's owner.

  1. Share album 4410 with Ava as a person, then with the group lab-tmp-sports-desk as a set:

POST $ISSUER/lab-api/photos/albums/4410/shares
Authorization: Bearer $TOKEN
Content-Type: application/json

{"relation":"viewer","subject":"group:<lab-tmp-sports-desk ID>#member"}

Add Ben to the group and see his access appear without touching the share. The sharing page shows the group's current size before the share is saved.

  1. Create a view-only link that expires in 14 days. The answer shows the link once; the store keeps only its hash. Open it in a private window, then revoke it alone and open it again: refused.

  2. Create a guest share for an outside editor with Cora as owner and a 30-day expiry, then list every external person with access to the album as its owner.

  3. Remove Ava's share, add photo 9014 two seconds later, and ask whether Ava may view it through a replica that is behind. The decision point refuses to answer from a copy older than the version stored with the photo, and the answer is a deny.

  4. Groups in this tenant cannot contain groups, so cycles cannot form. The planned store allows nesting for the exercise: create a two-group cycle and see the check stop with the reason limit_reached instead of searching forever.

Do today

  1. A bearer credential. As Ava, run authorize "openid photos.read offline_access", approve, and redeem. Keep the refresh token as well:

REFRESH=$(echo "$RESP" | jq -r .refresh_token)

TOKEN now works for whoever holds it, like a share link.

Why it matters: the service cannot tell whether the person presenting a bearer credential is the one it was given to, which is why the lesson designs links as credentials.

  1. Ask the source of truth. Introspect the token as the photo API:

read -rs CLIENT_SECRET   # lab-photo-api's secret
curl -s -u "<lab-photo-api client ID>:$CLIENT_SECRET" -d "token=$TOKEN" "$ISSUER/oauth/introspect" | jq '{active, scope, client_id, sub}'

It answers active: true.

Why it matters: a check that asks the issuer each time sees every change at once, at the cost of a request.

  1. Only the right party can cancel. Try to revoke Ava's token as lab-collage, a different application:

read -rs CLIENT_SECRET   # lab-collage's secret
curl -s -u "<lab-collage client ID>:$CLIENT_SECRET" -d "token=$TOKEN" "$ISSUER/oauth/revoke" | jq .

It is refused with unauthorized_client.

Why it matters: each grant, like each link, is its own record, cancelled by its owner. Another application cannot end someone else's share, and it learns that it cannot rather than being told everything is fine.

  1. Revoke it as the application that holds it, then introspect again: active: false.

curl -s -d "token=$TOKEN" -d "client_id=$CLIENT_ID" "$ISSUER/oauth/revoke" -w '%{http_code}\n'

Why it matters: the share is cancelled at the source.

  1. A local copy that lags. Validate the same token locally:

btl-lab verify "$TOKEN" --issuer "$ISSUER" --audience "$ISSUER/resource" --type at+jwt

The signature and lifetime checks still pass.

Why it matters: a check that reads a copy, here the self-contained token, keeps answering yes after the removal, exactly like the lesson's replica a few seconds behind. Freshness has to be designed in.

  1. Wait until 5 minutes after the token was issued and run the same btl-lab verify. It now fails on exp.

Why it matters: an expiry bounds how long a forgotten copy can say yes. It turns forgetting into removal, as the guest share's 30 days do.

Break it

  1. A leaked credential and its descendants. Use the refresh token from step 1 once; rotation returns a new pair:

curl -s "$ISSUER/oauth/token" -d grant_type=refresh_token -d "refresh_token=$REFRESH" -d "client_id=$CLIENT_ID" | jq '{scope, error}'

Send exactly the same command again with the old value. It is refused with invalid_grant, Audit records oauth.token rejected with reason refresh_replayed, and the new refresh token from the first call no longer works either.

Why it matters: a second use of a rotated token means it escaped, so the whole family is cut off, the way a removed share takes every derived link with it. With a reuse grace above 0 seconds, a quick retry would have been accepted instead and recorded as refresh_reuse_grace, which is why this lab sets it to 0.

Check your work

No automated checks: this lab is planned. In Audit, find oauth.introspect with reason other_client_token_found, oauth.revoke rejected with reason other_client_token, oauth.revoke with reason access_token_found, and oauth.token rejected with reason refresh_replayed.

Cleanup

  • Assign lab-printer-app back to the default access token manager and delete lab-tmp-short.

  • Keep lab-photo-api and lab-collage.

Missing infrastructure

  • G3 and G22, sharing in the photo library API. Shares to users, groups, links and guests as relationship facts; links stored as hashes, scoped to viewing, expiring and revocable one by one; guest shares with an owner and an expiry; versioned relationship writes with consistency tokens so no check mixes facts older than the change it depends on; and depth, visit and cycle limits that deny with limit_reached. Once it exists, the planned walkthrough runs as written with automated checks on the decision records.

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