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

Decide who may exchange which token, and what storage checks afterwards

Walk the authorization server's exchange checks with requests that fail at each one, confirm the capped lifetime and kept auth_time, and build storage's decision. Today, run the trust checks your tenant already makes.

PlannedUses your lab tenant

The lesson

Builds on: Scopes, resources, and audiences.

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

These steps are real today.

  1. You need lab-printer, lab-print-orders and lab-photo-api (with Resource server on) and their credentials in the shell as CLIENT_ID/CLIENT_SECRET, ORDERS_ID/ORDERS_SECRET and API_ID/API_SECRET.

  2. Confirm lab-printer does not allow the client_credentials grant and lab-print-orders does not have Resource server on.

  3. Get a fresh printer token AT for Ava with a lab-printer code flow for photos.read, as in the earlier token exchange labs.

Planned walkthrough

This walkthrough runs once your tenant supports token exchange (G6) with the planned resources and exchange policies (G55).

Planned setup

  1. On lab-tmp-photo-storage (create it if needed), set the exchange policy: accept access tokens whose aud is https://storage.lab.example; target https://archive.lab.example; map storage.read to archive.read; delegation; 60 seconds. lab-printer keeps no exchange grant.

  2. In OAuth > Access token managers, create lab-tmp-at-short (JWT, lifetime 90 seconds, with the auth_time mapping) and assign it to lab-printer.

Steps

  1. Who may exchange. lab-printer sends the exchange with its own valid token:

curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/token" \
  --data-urlencode grant_type=urn:ietf:params:oauth:grant-type:token-exchange --data-urlencode "subject_token=$AT" \
  --data-urlencode subject_token_type=urn:ietf:params:oauth:token-type:access_token \
  --data-urlencode resource=https://storage.lab.example -d scope=storage.read | jq .

The result is unauthorized_client.

Why it matters: without client authentication and a per-client permission, anyone holding a stolen token could use the token service to turn it into tokens for other services.

  1. The subject token belongs with its presenter. As lab-tmp-photo-storage, present Ava's printer token, whose audience is the photo API. The result is invalid_request, and Audit records invalid_subject_token (planned reason).

Why it matters: a client may exchange only tokens issued for the API it is registered as, so a token sent to the wrong service by mistake cannot be turned into new access.

  1. An expired or revoked subject. Revoke AT as lab-printer, then exchange it as lab-photo-api. The result is invalid_request.

  2. The lifetime cap. Sign in again so AT comes from lab-tmp-at-short, wait 60 seconds, and exchange it as lab-photo-api. expires_in is about 30, never past the subject token's exp.

  3. Authentication details are kept, not refreshed. The exchanged token's auth_time equals the subject token's.

Why it matters: an exchange must not let authority outlive the token behind it, nor make a sign-in look more recent or stronger than it was.

  1. Storage's decision. Start storage with btl-lab resource --port 8767 --mode introspect --audience https://storage.lab.example --read-scope storage.read --actor <lab-photo-api ID> (planned options). A delegation token from lab-photo-api is allowed. A token about Ava with any other current actor, or the photo API's own client credentials token with no act, is refused even with the right scope.

Why it matters: a recipient decides on the top-level claims and the current actor only, takes the subject from the validated token, never from a path or header, and still checks that the object is the subject's.

Do today

  1. Who may use a grant is decided per client today:

curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/token" -d grant_type=client_credentials | jq .

The result is unauthorized_client, and Audit records oauth.token rejected unauthorized_client for lab-printer.

  1. Who may read another client's token is decided per client too. Introspect Ava's printer token as the print orders job, then as the photo API:

curl -s -u "$ORDERS_ID:$ORDERS_SECRET" "$ISSUER/oauth/introspect" --data-urlencode "token=$AT" | jq '{active}'
btl-lab introspect "$AT"

The first prints {"active": false} (Audit token_not_found), the second active: true (Audit other_client_token_found). The Resource server flag is your tenant's trust decision about reading another client's token, the same kind of decision an exchange policy makes about presenting one.

  1. A token belongs to its client. As the print orders job, try to revoke Ava's printer token:

curl -s -u "$ORDERS_ID:$ORDERS_SECRET" "$ISSUER/oauth/revoke" --data-urlencode "token=$AT" | jq .

The result is unauthorized_client, and Audit records oauth.revoke rejected other_client_token.

  1. Revoked means unusable. Revoke AT as lab-printer, then introspect it as the photo API:

curl -s -o /dev/null -w '%{http_code}\n' -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/revoke" --data-urlencode "token=$AT"
btl-lab introspect "$AT"

200, then active: false. An exchange of this token would be refused in the same way as step 3 of the planned walkthrough.

Check your work

There are no automated checks while this lab is planned. For Do today, look in Lab Photos' Audit for oauth.token rejected unauthorized_client for lab-printer, oauth.introspect with token_not_found for lab-print-orders and other_client_token_found for lab-photo-api, oauth.revoke rejected other_client_token for lab-print-orders, and oauth.revoke succeeded access_token_found for lab-printer.

Cleanup

If you created lab-tmp-at-short, assign lab-printer back to the manager it used before and delete it. Run unset AT.

Missing infrastructure

  • G6 Token exchange, with the checks in the lesson's order driven by each client's exchange policy (permitted clients, accepted subject token audiences, targets, scope mapping, lifetime cap, kept auth_time and acr) and a tenant-visible record linking the subject token's jti to the issued one. The --read-scope and --actor options are planned additions to btl-lab resource.

  • G55 Tenant resource (API) registry for the storage and archive targets.

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