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

Send a token exchange request field by field and follow the new token

Send the lesson's exchange request, read issued_token_type and the missing scope, use the new token at storage, and see the subject token is neither used up nor linked. Today, trace the refusal by request ID.

PlannedUses your lab tenant

The lesson

Builds on: The problem token exchange solves.

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. You need lab-printer, lab-photo-api, their credentials in the shell, and a fresh printer token AT for Ava, as in Try three ways to call the storage service.

  1. In its own terminal, start the photo API, which accepts Ava's printer token:

btl-lab resource --port 8766 --mode introspect --audience "$ISSUER/resource"

Planned walkthrough

This walkthrough runs once your tenant supports token exchange (G6) with the planned resources and lab-photo-api exchange policy from the first token exchange lab (G55).

  1. In another terminal, start storage with btl-lab resource --port 8767 --mode introspect --audience https://storage.lab.example --read-scope storage.read. Send the full request, including the optional requested_token_type:

X=$(curl -s -D h.txt -u "$API_ID:$API_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 requested_token_type=urn:ietf:params:oauth:token-type:access_token \
  --data-urlencode resource=https://storage.lab.example -d scope=storage.read)
jq 'del(.access_token)' <<<"$X"; grep -i cache-control h.txt

The response has issued_token_type urn:ietf:params:oauth:token-type:access_token, token_type Bearer and expires_in 60, with Cache-Control: no-store. It has no scope, because the server issued exactly what was asked for, and no refresh token.

Why it matters: issued_token_type says what was issued, token_type says how to use it at an API. The new token arrives in access_token even though an exchange can issue other kinds.

  1. Look inside: ST=$(jq -r .access_token <<<"$X"); unset X; btl-lab decode "$ST". The header has typ at+jwt and your tenant's usual kid. The claims keep iss and Ava's sub, and change aud to storage, client_id to the photo API, scope to storage.read, and exp to 60 seconds after iat, with act naming the photo API.

Why it matters: to storage it is an ordinary access token from the same issuer, validated in the ordinary way.

  1. Use it: curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $ST" http://127.0.0.1:8767/objects/photo-1187 returns 200, and storage logs Ava as subject and the photo API as actor. The photo API may reuse ST for the album's other files during its minute, keyed by subject as well as audience.

  2. The subject token is not used up. Call the photo API with AT again: still 200.

  3. The two tokens are not linked. As lab-printer, revoke AT at $ISSUER/oauth/revoke, then call storage with ST within its minute: still allowed, and btl-lab introspect "$ST" still reports it active.

Why it matters: revocation does not carry across an exchange unless the deployment builds that in. The one-minute lifetime keeps the gap short. A planned tenant setting, "Revoke exchanged tokens when the subject token is revoked", would be a meaningful choice to expose, with this step as its test.

Do today

  1. Send exactly the step 1 request today, with a real AT:

curl -s -D h.txt -u "$API_ID:$API_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 requested_token_type=urn:ietf:params:oauth:token-type:access_token \
  --data-urlencode resource=https://storage.lab.example -d scope=storage.read | jq .
grep -i -E 'x-request-id|cache-control' h.txt

The result is {"error":"unsupported_grant_type",...}, with Cache-Control: no-store and an X-Request-ID.

  1. Find it. In Lab Photos, open Logs, filter outcome rejected, and look for the token endpoint summary that includes your request ID. Audit has no entry, because the request was refused before the client authenticated.

Why it matters: a request ID joins records even when no token was issued, which is exactly what an operator needs when an exchange fails several services deep.

  1. A token is not consumed by being used. Call the photo API twice with AT:

for i in 1 2; do curl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $AT" http://127.0.0.1:8766/photos; done

Both return 200. An exchange reads its subject token the same way, unless a token type is defined to be single-use.

Break it

  1. Planned: ask for requested_token_type=urn:ietf:params:oauth:token-type:jwt when the policy allows only access tokens. The result is invalid_request.

Check your work

There are no automated checks while this lab is planned. For Do today, look in Lab Photos' Logs for the refused exchange by its request ID, and in Audit for the photo API's oauth.introspect events with other_client_token_found.

Cleanup

Stop the local services and delete h.txt. Run unset ST.

Missing infrastructure

  • G6 Token exchange: the grant, issued_token_type, requested_token_type, the 60-second capped lifetime, delegation act, and a tenant-visible exchange record.

  • G55 Tenant resource (API) registry for the storage target and its scopes.

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