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, as the photo API, whether to trust a token

Create the client that plays the photo API, check a token's issuer, signature, audience, lifetime and revocation as an API must, and see a correctly signed token for another API refused.

Partly readyUses your lab tenant

The lesson

Builds on: The problem OAuth solves.

New to the labs? Start with the lab toolkit and the shared cast and names every lab uses.

Partly ready. Most of this lab runs today. Steps that wait on platform features are marked, and Missing infrastructure says what they need.

Your progress

Press Start before you begin. Only events your tenant records after that count, in the order below. Checking reads your tenant's Audit, so you need Audit read access in it.

  1. Create the lab-photo-api client

    Recorded as tenant.oauth.clients.create succeeded.

  2. The photo API asks the authorization server about a token

    Recorded as oauth.introspect succeeded (other_client_token_found) for lab-photo-api.

  3. The photo API cannot revoke the printer's grant

    Recorded as oauth.revoke rejected (other_client_token) for lab-photo-api.

  4. The client revokes its own token

    Recorded as oauth.revoke succeeded (access_token_found).

Request console

Requests in this lab can be sent from this page to your tenant: open one and choose Send. Fill in the values below first. They stay in this page's memory and are gone when you leave; secrets are never stored or sent anywhere except the request you send.

Setup

  1. Press Start on this page.

  2. In the tenant portal, open OAuth > Clients > Create client and choose Blank client. Name it lab-photo-api, type Confidential, tick Resource server introspection, choose no grants and no redirect URIs, tick Enabled, and save. Copy its client ID.

  3. Open Access Token Management > Client authentication secret, choose lab-photo-api and Rotate client secret. The secret is shown once.

  4. In Access Token Management, create a temporary manager named lab-tmp-other-audience: format Signed JWT, an active signing key, lifetime 600 seconds, and under Standard claims set aud to https://api.printlab.example. Save it, but do not assign it yet.

  5. Set the shell variables. btl-lab introspect and btl-lab resource --mode introspect call the tenant as lab-photo-api with them.

export ISSUER="https://tenant-<id>.beyondthelogin.dev"
export DECODER_ID="<Token Decoder client ID>"
export API_ID="<lab-photo-api client ID>"
read -rs API_SECRET && export API_SECRET   # paste the secret, then press Enter

Walkthrough

  1. Get a fresh Token Decoder token for Ava with Scopes openid photos.read, and copy the access token: read -r TOKEN.

  1. Does the API recognize the authority that issued it? Compare the token's key ID with the keys your tenant publishes, then run the full validation.

btl-lab decode "$TOKEN"          # header: alg ES256, typ at+jwt, and a kid
btl-lab discover "$ISSUER"       # the JWKS lists the same kid
btl-lab verify "$TOKEN" --issuer "$ISSUER" --audience "$ISSUER/resource" --type at+jwt

verify prints each check: signature against the published key, alg, typ, iss, aud, and exp with clock skew. All pass.

Why it matters: the lesson's first API question is whether it recognizes the issuer. Signature verification answers that, and the remaining lines answer the separate questions of intended API and lifetime.

  1. Play the photo API. In a second terminal, start the toolkit's local resource server, which validates JWTs itself, enforces aud and scopes, and answers with RFC 6750 challenges.

btl-lab resource --mode jwt

Back in the first terminal, call it with the token: curl -s -i -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8766/photos returns 200, and the resource server's terminal prints the checks it made.

  1. Ask the authorization server instead, as an API with opaque tokens must.

btl-lab introspect "$TOKEN"

The answer is active: true, the Token Decoder's client_id, Ava's sub and scope: "openid photos.read". Because lab-photo-api is marked as a resource server, the tenant lets it introspect tokens issued to other clients.

  1. Each participant checks only what is its responsibility. Try to end the decoder's grant as the photo API.

curl -s -u "$API_ID:$API_SECRET" -d "token=$TOKEN" "$ISSUER/oauth/revoke"

The answer is 400 with {"error":"unauthorized_client"}: a client can revoke only its own tokens. The token stays active.

Why it matters: the API decides whether to accept a request; it does not get to withdraw the access Ava gave another application.

  1. A correctly signed token meant for a different API. In OAuth > Clients > Token Decoder, set Access token manager to lab-tmp-other-audience and save. This revokes the decoder's existing tokens. Get a new decoder token and run the same checks.

read -r TOKEN   # the new access token
btl-lab verify "$TOKEN" --issuer "$ISSUER" --audience "$ISSUER/resource" --type at+jwt
curl -s -i -H "Authorization: Bearer $TOKEN" http://127.0.0.1:8766/photos

The signature check passes and the aud check fails, because aud is https://api.printlab.example. The resource server answers 401 with WWW-Authenticate: Bearer error="invalid_token".

Why it matters: a correctly signed token intended for another API is not permission at this one. Audience is a separate check from signature.

Restore: in OAuth > Clients > Token Decoder, set Access token manager back to Default access tokens and save.

  1. Optional, with your Lab Mail tenant: get a Token Decoder token there and run btl-lab introspect on it against Lab Photos. The answer is {"active": false}, and btl-lab verify against $ISSUER fails because its kid is not in this tenant's JWKS.

  1. Look back at the 200 from step 3. The resource server already returned its answer. Nothing you do to the token now can take that response back.

Why it matters: this is the boundary after access is granted. Revocation and short lifetimes stop future requests; they cannot make a copy the printer already downloaded disappear.

Planned walkthrough

These steps need the hosted lab API (G3), resource indicators (G16) and per-resource authorization (G22).

  1. Call the hosted photo API with Ava's token for one of Ben's albums. It answers 403, because a valid token is not permission to retrieve whichever album identifier appears in the URL.

curl -s -i -H "Authorization: Bearer $TOKEN" "$ISSUER/lab-api/photos/albums/<one of Ben's album IDs>"
  1. Ask for a token for https://api.printlab.example per request with the resource parameter instead of changing the client's token manager, then see the hosted photo API refuse it for the wrong audience.

Break it

  1. Get a fresh decoder token for Ava (read -r TOKEN). Confirm that the local resource server in jwt mode accepts it, then revoke it as the decoder, which owns it.

POST$ISSUER/oauth/revoke Open in console
POST $ISSUER/oauth/revoke HTTP/1.1
Content-Type: application/x-www-form-urlencoded

client_id=$DECODER_ID&token=$TOKEN
  1. btl-lab introspect "$TOKEN" now returns {"active": false}. Call http://127.0.0.1:8766/photos again: the jwt-mode resource server still answers 200, because the signature and exp are still fine.

  2. Stop the resource server and restart it with btl-lab resource --mode introspect. The same request now gets 401 invalid_token.

An API that validates JWTs only locally keeps honoring a revoked token until it expires. Short lifetimes and introspection are the two answers this tenant offers.

Check your work

Press Check my progress. In tenant Audit you can also find tenant.oauth.clients.create and tenant.oauth.credentials.rotate for lab-photo-api, tenant.oauth.managers.create for lab-tmp-other-audience, oauth.introspect succeeded other_client_token_found with actor lab-photo-api, oauth.revoke rejected other_client_token, and oauth.revoke succeeded access_token_found.

Cleanup

  1. Confirm the Token Decoder uses Default access tokens.

  2. Delete the lab-tmp-other-audience manager.

  3. Keep lab-photo-api and its secret. Later labs use it to play the photo API.

Missing infrastructure

  • G3, sample protected resource API. The toolkit's local resource server stands in for the photo API today. A hosted per-tenant API at $ISSUER/lab-api/photos/ would validate tokens, answer with WWW-Authenticate challenges and write its own Audit events, so the API's decisions in steps 3 and 6 appear in tenant history too.

  • G16, resource indicators. The client would ask for a token for https://api.printlab.example per request instead of the tenant changing a token manager.

  • G22, per-resource authorization. The full lab has Ava's token request Ben's album and receive 403, showing that a valid token is not permission to any identifier in a URL.

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