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

Trace the generations of single sign-on through your tenant

Replay each era of the history with a real exchange in Lab Photos, from passwords and ticket-like codes to OAuth, OpenID Connect, passkeys and today's defaults, and note which problem each one solved.

Partly readyUses your lab tenant

The lesson

Builds on: Giving another application limited access.

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. Run a central web sign-in for the printer

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

  2. See the implicit response type refused by default

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

  3. Get a token for software acting on its own

    Recorded as oauth.token succeeded for lab-print-orders.

  4. Allow the implicit response type for one experiment

    Recorded as tenant.oauth.policy.update succeeded.

  5. Receive an access token in the URL fragment

    Recorded as oauth.authorize succeeded (tokens_issued) 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

You need Ava with her password and passkey, lab-printer, lab-print-orders and the toolkit.

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

  2. Set the variables and helpers from the earlier labs:

ISSUER=https://tenant-<id>.beyondthelogin.dev
CLIENT_ID=<lab-printer client ID>
read -rs CLIENT_SECRET
ORDERS_ID=<lab-print-orders client ID>
read -rs ORDERS_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)"; }

Walkthrough

  1. The 1960s, a shared computer. Users lists the accounts, and /login checks a password against a stored representation of it. You did exactly this in the Proving control of an account lab.

Why it matters: a username identifies the account and a password supplies evidence. Everything later builds on that pair.

  1. The 1980s, across a network. Sign in to $ISSUER/token-decoder as Ava. Then run fresh; authz openid and open it in the same window: a code for lab-printer arrives without a password.

Note: Kerberos is not part of a web tenant, so this is an analogy. The tenant's session cookie plays the part of a ticket-granting ticket, and each authorization code plays the part of a service ticket.

Why it matters: after one authentication, a client obtains evidence for each service without the password travelling again.

  1. The early 2000s, web SSO. Redeem the code from step 2 and label each part of the flow with its CAS equivalent: the redirect to the central server, the code returned through the browser (the service ticket), the direct redeem call (ticket validation), and the application starting its own session.

CODE=<code from the callback>
redeem | jq 'keys'

Why it matters: the applications never shared a browser cookie. They used a central service to establish who had authenticated.

  1. The 2000s, federation. Read what your tenant advertises:

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

Look for SAML or WS-Federation endpoints: there are none, and curl -s -o /dev/null -w '%{http_code}\n' "$ISSUER/saml/metadata" returns 404.

Why it matters: your tenant speaks the newer generation. Organizations often run SAML and OpenID Connect side by side, which this tenant cannot show yet (G25).

  1. OAuth is not sign-in. Run fresh; authz photos.read, with no openid, sign in and redeem: you get an access token and no ID token. Then run fresh; authz openid%20photos.read and redeem again: now an ID token arrives as well.

Why it matters: OAuth 2.0 granted API access. OpenID Connect added the standard sign-in result that OAuth alone does not define.

  1. Strengthening the sign-in. In the Token Decoder, ask for a fresh sign-in and use Ava's passkey, then do it again with her password and code. Both ID tokens have the same structure; only amr differs.

Why it matters: passwordless authentication changes how Ava proves herself to the provider, not how the provider tells applications about it.

  1. Today's guidance in your configuration. Flow policy shows the implicit response types off by default, PKCE is required, every authorization response carries iss, and refresh tokens rotate. Ask for an access token straight from the authorization endpoint:

fresh; curl -s -o /dev/null -w '%{redirect_url}\n' "$ISSUER/oauth/authorize?response_type=token&client_id=$CLIENT_ID&redirect_uri=$(node -p 'encodeURIComponent(process.argv[1])' "$REDIRECT")&scope=photos.read&state=$STATE"

Expected: a redirect with error=unauthorized_client.

Why it matters: RFC 9700 consolidated the deployment lessons, and they show up here as defaults rather than as a checklist.

  1. Software acting on its own.

curl -s -u "$ORDERS_ID:$ORDERS_SECRET" -d grant_type=client_credentials -d scope=prints.create "$ISSUER/oauth/token" | jq -r .access_token | xargs btl-lab decode

Expected: sub is the job's own client ID.

Why it matters: not every identity in an SSO landscape is a person. Services and agents need their own identities and carefully limited authority.

Planned walkthrough

These steps need a SAML identity provider (G25) and inbound federation (G26).

  1. Register a lab service provider in Lab Photos, exchange metadata, and sign Ava in to it. Compare the SAML assertion with the ID token from step 5: issuer, subject, audience, authentication time and attributes.

  2. Add Lab Mail as an external provider and sign in to Lab Photos with Lab Mail's Ava, the way the lesson's publisher accepts a university's sign-in.

Break it

  1. A contained, temporary experiment. In Flow policy allow the implicit token response type and the fragment response mode, and allow both on lab-printer. Repeat step 7 in the private window where Ava is signed in, by opening the same authorize URL in the browser instead of curl. The access token arrives in the URL fragment, visible in the address bar and browser history.

Why it matters: this is why later guidance moved away from the implicit flow. Tokens in URLs leak into history, logs and other pages.

Restore: remove the token response type and the fragment response mode from lab-printer and from Flow policy, save both, and repeat step 7 to confirm error=unauthorized_client returns.

Check your work

Press Check my progress. It looks for:

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

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

  • oauth.token succeeded for lab-print-orders.

  • tenant.oauth.policy.update succeeded (the temporary change; Audit also shows the restore and tenant.oauth.clients.update).

  • oauth.authorize succeeded with reason tokens_issued for lab-printer.

Cleanup

  1. Confirm that the implicit response type is off in Flow policy and on lab-printer.

  2. Close the browser window that holds the token in its history, and clear that history entry.

Missing infrastructure

  • G25 SAML. The tenant offers no SAML identity provider, so it cannot show an assertion beside an ID token or exchange metadata with a service provider.

  • G26 Inbound federation. The tenant cannot accept another organization's sign-in, so it cannot play the publisher in the lesson's federation example.

  • Kerberos, NTLM and CAS are historical context and are not planned as tenant features; the lab covers them by analogy.

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