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

See what the browser carries in the code flow, and what it does not

Watch a code, not a token, come back through the browser, see the API refuse the code, and receive the token only in the printer's direct, authenticated request.

ReadyUses your lab tenant

The lesson

Builds on: Public and confidential clients.

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

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. Ava approves and the browser carries back a code

    Recorded as oauth.authorize succeeded (code_issued) for lab-printer about [email protected].

  2. A code without the printer's credentials is refused

    Recorded as oauth.token rejected (unsupported_auth_method) for lab-printer.

  3. The printer's backend exchanges the code

    Recorded as oauth.token succeeded for lab-printer.

  4. The API accepts the access token

    Recorded as oidc.userinfo succeeded (userinfo_served) for lab-printer.

  5. A token response through the browser is refused

    Recorded as oauth.authorize rejected (unauthorized_client) for lab-printer.

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

The eight labs in this section grow one client, lab-printer, from its first request to a fully checked exchange.

  1. Set the shell variables for lab-printer (from the Public and confidential clients lab), then press Start on this page.

export ISSUER="https://tenant-<id>.beyondthelogin.dev"
export CLIENT_ID="<lab-printer client ID>"
read -rs CLIENT_SECRET
export REDIRECT_URI="https://beyondthelogin.dev/lab/callback/"   # or http://127.0.0.1:8765/callback with btl-lab callback running
enc() { jq -rn --arg v "$1" '$v|@uri'; }

Walkthrough

  1. Build the printer's request. It includes openid so that the UserInfo endpoint can stand in for the photo API.

eval "$(btl-lab pkce)"; eval "$(btl-lab state)"
echo "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$(enc "$REDIRECT_URI")&scope=$(enc 'openid photos.read')&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256"
  1. Open it, sign in as Ava and approve. The callback shows code, state and iss. Copy the code: read -r CODE.

Why it matters: everything that crossed the browser holds a short-lived code. No token was placed in a URL, so none can leak through history, logs or a referrer.

  1. Offer the code to the API as if it were a token.

GET$ISSUER/oidc/userinfo Open in console
GET $ISSUER/oidc/userinfo HTTP/1.1
Authorization: Bearer $CODE

The answer is 401 with WWW-Authenticate: Bearer error="invalid_token".

Why it matters: the code is an intermediate credential. The API does not accept it.

  1. Try the exchange from somewhere that has the code but not the printer's secret: a second terminal, as if the code had been copied out of the browser.

curl -s -i -d grant_type=authorization_code -d "code=$CODE" --data-urlencode "redirect_uri=$REDIRECT_URI" \
  -d "code_verifier=$VERIFIER" -d "client_id=$CLIENT_ID" "$ISSUER/oauth/token"

The answer is 401 invalid_client. The code is still unused.

  1. Exchange it as the printer's backend, with its credentials and its PKCE verifier.

curl -s -i -u "$CLIENT_ID:$CLIENT_SECRET" -d grant_type=authorization_code -d "code=$CODE" \
  --data-urlencode "redirect_uri=$REDIRECT_URI" -d "code_verifier=$VERIFIER" "$ISSUER/oauth/token"

The answer is 200 with Cache-Control: no-store and a JSON body holding access_token, token_type, expires_in, scope and id_token. Copy the access token: read -r TOKEN.

Why it matters: the token arrives in the body of a direct response. That request is where the server could check the printer's secret and its PKCE verifier, two checks a token sent back through the browser would allow neither of.

  1. Call the API with the token: repeat step 3 with $TOKEN instead of $CODE. The answer is 200 with Ava's sub.

  1. Search your browser history for the callback address. The entry holds the code; no access token appears anywhere in history.

Break it

  1. Ask for the token to come straight back through the browser instead.

echo "$ISSUER/oauth/authorize?response_type=token&client_id=$CLIENT_ID&redirect_uri=$(enc "$REDIRECT_URI")&scope=photos.read&state=$STATE"

The callback carries error=unauthorized_client, state and iss. Neither the tenant's flow policy nor the printer's registration allows a token response, which is the safe default the lesson argues for.

Check your work

Press Check my progress. Logs also has a summary row for oidc.userinfo rejected invalid_token with status 401: the code offered as a token.

Cleanup

None. Keep the shell variables for the next lab.

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