OAUTH 2.0 · LAB
Label your tenant's tokens, then send a subject token and an actor token together
Label each token Lab Photos issues with its RFC 8693 type identifier and ask the tenant which it recognizes. Then plan a support console exchange where Ben acts for Ava.
PlannedUses your lab tenant
The lesson
Builds on: Delegation and impersonation.
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.
- G6 Token exchange (RFC 8693)
Setup
These steps are real today.
In Lab Photos, confirm OAuth > Flow policy allows
refresh_token. Onlab-printer, allow therefresh_tokengrant and assignopenid,photos.readandoffline_access.Keep
CLIENT_IDandCLIENT_SECRETforlab-printerin the shell.
Planned walkthrough
This walkthrough runs once your tenant supports token exchange with actor tokens and policy conditions on the actor (G6). Ben, the help desk member of the lab cast, plays the lesson's support agent, and Ava is the customer.
Planned setup
In Groups, create
lab-tmp-support-agentswith Ben as its only member, and set a password for[email protected].In OAuth > Clients, create
lab-tmp-support-console(confidential web, redirect URIhttp://127.0.0.1:8765/callback, PKCE required, scopeopenid). Store its credentials asCONSOLE_IDandCONSOLE_SECRETwithread -rs.In OAuth > Resources (planned), add the print API at
https://prints.lab.exampleowning a read scopeprints.read.On
lab-tmp-support-console, set the exchange policy (planned): subject tokens are access tokens issued tolab-printer; actor tokens are ID tokens issued tolab-tmp-support-consolewhose subject is inlab-tmp-support-agents; targethttps://prints.lab.examplewithprints.read; delegation; lifetime 10 minutes.
Steps
Ben signs in to the console with an ordinary code flow for
lab-tmp-support-consoleand scopeopenid, which gives the consoleBEN_ID_TOKEN. Ava's printer access token isAT.The console exchanges both tokens:
curl -s -u "$CONSOLE_ID:$CONSOLE_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 "actor_token=$BEN_ID_TOKEN" --data-urlencode actor_token_type=urn:ietf:params:oauth:token-type:id_token \
--data-urlencode resource=https://prints.lab.example -d scope=prints.read | jq -r .access_token
Decoded, the new token has sub Ava, client_id the console and act.sub Ben.
Why it matters: one request identifies three parties. Client authentication speaks for the console, the actor token for Ben, and the subject token for Ava. Several agents share the console, so only an actor token can say which of them is acting.
Remove Ben from
lab-tmp-support-agentsand repeat step 2. The result isinvalid_request, and Audit recordsoauth.tokenrejectedinvalid_actor_token(planned reason). Add Ben back.
Why it matters: the server applies its own current policy to the actor, whatever any token says, much as it would compare a may_act claim with the party asking to act.
Send
actor_token_typewithoutactor_token. The result isinvalid_request.
Why it matters: the type is required with an actor token and must not be sent without one.
Do today
Collect every token type your tenant issues. Start
btl-lab callbackand run alab-printercode flow foropenid photos.read offline_access:
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=openid%20photos.read%20offline_access&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256"
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"); unset CODE
Label each token with the lesson's identifiers: .access_token is urn:ietf:params:oauth:token-type:access_token, .refresh_token is urn:ietf:params:oauth:token-type:refresh_token, and .id_token is urn:ietf:params:oauth:token-type:id_token. The access token is also a JWT, but from its own issuer's point of view it is labeled by purpose, not format.
Ask the tenant which of them it can read as tokens it issued:
for f in access_token refresh_token id_token; do printf '%s: ' "$f"
curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/introspect" --data-urlencode "token=$(jq -r .$f <<<"$RESP")" | jq -c '{active, token_type}'; done
unset RESP
The access token is active with token_type Bearer, the refresh token is active with no token_type, and the ID token is active: false: it is a statement for the client, not a credential the server tracks. The label exists because a server accepting several kinds of token must know which one it is reading.
No configuration can grant permission to act. In OAuth > Access token managers, try to add a claim mapping named
may_actto any manager. The name is reserved, so the change is refused.
Check your work
There are no automated checks while this lab is planned. For Do today, look in Lab Photos' Audit for oauth.token succeeded for lab-printer and three oauth.introspect events for lab-printer, two with access_token_found and refresh_token_found and one with token_not_found.
Cleanup
Delete lab-tmp-support-console and lab-tmp-support-agents if you created them for the planned steps. Keep the lab-printer settings.
Missing infrastructure
G6 Token exchange, including actor tokens, the six token type identifiers,
requested_token_type, and exchange policy conditions on the actor such as group membership. Issuingmay_actgrant tokens, as in the lesson's "Let the agent view my order", would be a further addition on top of G6; the planned walkthrough uses the server's own group policy instead.