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.
- G25 SAML
- G26 Inbound federation (external OIDC or social IdP) and account linking
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.
Sign in to start this lab and check your progress. Log in or create an account.
Run a central web sign-in for the printer
Recorded as
oauth.authorizesucceeded (code_issued) forlab-printerabout[email protected].See the implicit response type refused by default
Recorded as
oauth.authorizerejected (unauthorized_client) forlab-printer.Get a token for software acting on its own
Recorded as
oauth.tokensucceeded forlab-print-orders.Allow the implicit response type for one experiment
Recorded as
tenant.oauth.policy.updatesucceeded.Receive an access token in the URL fragment
Recorded as
oauth.authorizesucceeded (tokens_issued) forlab-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.
On this lab page choose Lab Photos and press Start.
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
The 1960s, a shared computer. Users lists the accounts, and
/loginchecks 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.
The 1980s, across a network. Sign in to
$ISSUER/token-decoderas Ava. Then runfresh; authz openidand open it in the same window: a code forlab-printerarrives 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.
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
redeemcall (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.
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/jsonLook 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).
OAuth is not sign-in. Run
fresh; authz photos.read, with noopenid, sign in and redeem: you get an access token and no ID token. Then runfresh; authz openid%20photos.readand 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.
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
amrdiffers.
Why it matters: passwordless authentication changes how Ava proves herself to the provider, not how the provider tells applications about it.
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.
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).
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.
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
A contained, temporary experiment. In Flow policy allow the implicit
tokenresponse type and thefragmentresponse mode, and allow both onlab-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.authorizesucceeded with reasoncode_issuedforlab-printerand Ava.oauth.authorizerejected with reasonunauthorized_clientforlab-printer.oauth.tokensucceeded forlab-print-orders.tenant.oauth.policy.updatesucceeded (the temporary change; Audit also shows the restore andtenant.oauth.clients.update).oauth.authorizesucceeded with reasontokens_issuedforlab-printer.
Cleanup
Confirm that the implicit response type is off in Flow policy and on
lab-printer.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.