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

Exchange for a narrower token and see which targets and scopes are refused

Name the storage target by resource and by audience, let the policy map photos.read to storage.read, and meet invalid_scope and invalid_target. Today, practice the same limits with client credentials.

PlannedUses your lab tenant

The lesson

Builds on: Following a token exchange.

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-photo-api with the client_credentials grant, the photos.read scope and the manager lab-tmp-at-storage, its credentials in the shell, and a fresh printer token AT for Ava, all from Try three ways to call the storage service. Confirm photos.delete exists in Lab Photos and is not assigned to lab-photo-api.

Planned walkthrough

This walkthrough runs once your tenant supports token exchange (G6) with resource indicators semantics in the exchange (G16) and a resource registry with logical names (G55), using the planned exchange policy from the first token exchange lab.

Planned setup

  1. Create the confidential client lab-tmp-photo-storage, the storage service's own registration. In the registry (planned), give the storage resource the logical name lab-tmp-photo-storage, so audience can name it. Also register the sharing API at https://share.lab.example, which lab-photo-api's exchange policy does not list.

Steps

Each step sends the exchange from the first lab with a different target or scope. This helper keeps the requests short:

exchange() { curl -s -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 "$@" | jq 'del(.access_token)'; }
  1. Name the target by location: exchange --data-urlencode resource=https://storage.lab.example -d scope=storage.read. The token's aud is https://storage.lab.example.

  2. Name it logically: exchange -d audience=lab-tmp-photo-storage -d scope=storage.read. The same aud.

Why it matters: resource names where the token will be used and audience names the service as both sides know it. Either way the authorization server decides aud, and the storage service's audience check gives an exchanged token no exception.

  1. Ask for more than the subject token allows. Ava's printer token carries only photos.read: exchange --data-urlencode resource=https://storage.lab.example -d scope=storage.delete returns invalid_scope.

Why it matters: no policy row turns read access into delete access. An exchange cannot add what Ava never allowed the printer.

  1. Ask for both: exchange --data-urlencode resource=https://storage.lab.example -d 'scope=storage.read storage.delete'. Lab Photos issues storage.read alone, and the response includes "scope": "storage.read" because it differs from the request.

  2. A real target this client may not reach: exchange --data-urlencode resource=https://share.lab.example -d scope=photos.share returns invalid_target.

Why it matters: a target that exists is not a target this client may reach through exchange.

  1. Two targets at once: exchange --data-urlencode resource=https://storage.lab.example --data-urlencode resource=https://archive.lab.example -d 'scope=storage.read archive.read' returns invalid_target.

Why it matters: one token valid at both services is exactly what audience restriction avoids, and an exchange costs only one round trip, so the photo API asks for one target when the call is about to happen.

Do today

  1. Per-client scope limits are real. Ask for an exclusive scope lab-photo-api is not assigned, then for a mix:

curl -s -u "$API_ID:$API_SECRET" "$ISSUER/oauth/token" -d grant_type=client_credentials -d scope=photos.delete | jq .
curl -s -u "$API_ID:$API_SECRET" "$ISSUER/oauth/token" -d grant_type=client_credentials -d 'scope=photos.read photos.delete' | jq .

Both return invalid_scope, and Audit records oauth.token rejected invalid_scope for lab-photo-api. Your tenant refuses the whole request rather than issuing the allowed part, the stricter of the two behaviors the lesson describes for step 4.

  1. Leave the scope out:

RESP=$(curl -s -u "$API_ID:$API_SECRET" "$ISSUER/oauth/token" -d grant_type=client_credentials)
jq 'del(.access_token)' <<<"$RESP"; CT=$(jq -r .access_token <<<"$RESP"); unset RESP

The response has no scope member, and the token carries none: your tenant's policy grants nothing that was not requested. A client that needs particular access asks for it and checks what came back.

  1. The server decides the audience. Run btl-lab decode "$CT": aud is https://storage.lab.example, set by lab-tmp-at-storage, whatever the request said. Sending two resource values today is refused outright with invalid_request, before client authentication.

Break it

  1. Planned: ask for scope=photos.read with resource=https://storage.lab.example. The scope has no meaning at storage, and Lab Photos answers with its documented choice, invalid_target or invalid_scope. Either way, sending the same request again changes nothing.

Check your work

There are no automated checks while this lab is planned. For Do today, look in Lab Photos' Audit for two oauth.token rejections with invalid_scope and one success for lab-photo-api.

Cleanup

Delete lab-tmp-photo-storage if you created it for the planned steps. Keep the storage setup for the next lab. Run unset CT.

Missing infrastructure

  • G6 Token exchange with a scope mapping per target in the exchange policy.

  • G16 Resource indicators semantics in the exchange: resource and invalid_target, including too many targets.

  • G55 Tenant resource (API) registry with logical names for audience.

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