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

IDENTITY FUNDAMENTALS · LAB

Read real requests, redirects and errors from your tenant

Install the lab toolkit, register the photo printer, then read real URLs, headers, status codes, redirects and JSON as your tenant sends the browser to sign in and back to the application.

ReadyUses your lab tenant

The lesson

Builds on: Proving control of an account.

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. Register the photo printer as a client

    Recorded as tenant.oauth.clients.create succeeded.

  2. Ask for a silent sign-in with no session

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

  3. Sign in as Ava and get a code for the printer

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

  4. Exchange the code directly at the token endpoint

    Recorded as oauth.token succeeded for lab-printer.

  5. See a request without PKCE sent back with an error

    Recorded as oauth.authorize rejected (pkce_required) 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

This lab installs the lab toolkit that later labs use. You need bash (Git Bash on Windows is fine), curl, jq and Node.js 18 or later.

  1. On this lab page choose Lab Photos and press Start.

  2. Download the toolkit to your home directory and check it before you run it. Compare the printed SHA-256 value with the one shown on the toolkit page. If they differ, delete the file and do not run it.

cd ~
curl -fsSLO https://beyondthelogin.dev/lab/toolkit/btl-lab.mjs
sha256sum btl-lab.mjs          # on macOS: shasum -a 256 btl-lab.mjs
alias btl-lab="node ~/btl-lab.mjs"
btl-lab env

Note: the toolkit only reads, validates, relays, or acts as your own client. It never creates, signs or alters a token. Add the alias to your shell profile so later labs can use btl-lab directly.

  1. Clients > Create client with the web application preset:

    • name lab-printer, confidential

    • redirect URIs http://127.0.0.1:8765/callback and https://beyondthelogin.dev/lab/callback/

    • grant: authorization code, PKCE Required (the default)

  2. Keep the client's details in the shell. The secret is shown once.

ISSUER=https://tenant-<id>.beyondthelogin.dev
CLIENT_ID=<lab-printer client ID>
read -rs CLIENT_SECRET
REDIRECT=http://127.0.0.1:8765/callback
authz() { echo "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=$(node -p 'encodeURIComponent(process.argv[1])' "$REDIRECT")&scope=$1&state=$STATE&nonce=$NONCE&code_challenge=$CHALLENGE&code_challenge_method=S256"; }
redeem() { curl -s -u "$CLIENT_ID:$CLIENT_SECRET" -d grant_type=authorization_code --data-urlencode "code=$CODE" --data-urlencode "redirect_uri=$REDIRECT" -d code_verifier="$VERIFIER" "$ISSUER/oauth/token"; }
fresh() { eval "$(btl-lab pkce)"; eval "$(btl-lab state)"; }

To receive codes, run btl-lab callback in a second terminal before each browser sign-in; it listens on the loopback address and prints code, state and iss. Without a terminal handy, set REDIRECT=https://beyondthelogin.dev/lab/callback/ instead and read them on the hosted callback page, which never exchanges or stores them.

Walkthrough

  1. Read a JSON API response. Open this request in the console, or run it with curl -i:

GET$ISSUER/.well-known/openid-configuration Open in console
GET $ISSUER/.well-known/openid-configuration HTTP/1.1
Accept: application/json

Expected (abbreviated): 200, content-type: application/json, an x-request-id header, and a body with issuer, authorization_endpoint, token_endpoint and jwks_uri. Then run btl-lab discover "$ISSUER" for a summary of the same document and the key set it points to.

Why it matters: these are the parts the lesson named in its album request: scheme, host, path, method, headers, status and a JSON body.

  1. Ask for a private page without signing in:

curl -i "$ISSUER/account"
curl -sL -o /dev/null -w '%{http_code} %{url_effective}\n' "$ISSUER/account"

Expected: 303 with location: /login?continue=%2Faccount, and with -L curl follows it to the sign-in page (200, HTML).

Why it matters: this is the lesson's private album and its redirect to a sign-in page, on a real server. The Location header made the client send a new request.

  1. Call an API without credentials:

GET$ISSUER/oidc/userinfo Open in console
GET $ISSUER/oidc/userinfo HTTP/1.1
Accept: application/json

Expected: 401 with www-authenticate: Bearer.

Why it matters: a program calling an API gets an error it can act on, not a sign-in page.

  1. Ask the authorization server to sign in silently on behalf of lab-printer:

fresh; curl -si "$(authz openid)&prompt=none" | grep -i '^location'

Expected: 303 with a location back to your REDIRECT carrying error=login_required, your state and iss.

Why it matters: the identity service sends the browser back to the application's registered address. state comes back unchanged, so the application can connect this response to the request it sent.

  1. The same request without prompt=none:

fresh; curl -si "$(authz openid)" | grep -iE '^(HTTP|content-type|set-cookie)'

Expected: 200, content-type: text/html, and set-cookie: __Host-btl-oauth-tx=...; Path=/; Secure; HttpOnly; SameSite=Lax; Max-Age=600.

Why it matters: the server keeps this sign-in attempt in a short-lived cookie, so that the next request, the form post, can be connected to it. The next lab follows that idea into sessions.

  1. Now in a browser. Run fresh; authz openid and copy the printed URL. Open DevTools > Network with "Preserve log" ticked, paste the URL, and sign in as Ava with her password and authenticator code. Approve the consent screen. Network shows the chain: the authorize page, the sign-in post, a 303 to /login/verify, then a 303 to your redirect URI with code, state and iss.

Why it matters: each Location header made the browser send a new request, and the code reached the application through the browser.

  1. Exchange the code directly, without the browser:

CODE=<code from the callback>
redeem | jq '{token_type, expires_in, scope, has_id_token: (.id_token != null)}'

Why it matters: redirects travel through the browser, but this call goes straight from the application to the authorization server. The OAuth lessons build on that split between front channel and back channel.

  1. Open Audit and find oauth.authorize with reason code_issued and oauth.token succeeded for lab-printer. Open an event and compare its request ID with an x-request-id header you saw.

Why it matters: request IDs connect what a client saw with what the server recorded about the same request.

Break it

  1. Run fresh and take authz openid, but replace the redirect_uri value with https%3A%2F%2Fevil.example%2Fcallback. Expected: a 400 HTML error page and no Location header.

Why it matters: a server that redirected anywhere it was asked would hand codes to whoever named the address.

  1. Take a fresh authz openid URL and remove code_challenge and code_challenge_method. Expected: a 303 back to the registered redirect URI with error=invalid_request, because lab-printer requires PKCE. Audit shows reason pkce_required.

Check your work

Press Check my progress. It looks for:

  • tenant.oauth.clients.create succeeded (lab-printer).

  • oauth.authorize rejected with reason login_required for lab-printer.

  • oauth.authorize succeeded with reason code_issued for lab-printer and Ava.

  • oauth.token succeeded for lab-printer.

  • oauth.authorize rejected with reason pkce_required for lab-printer.

Cleanup

  1. Keep lab-printer, its secret and the toolkit. Later labs use them.

  2. Close the private window. Nothing else changed.

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