AUTHENTICATION METHODS · LAB
Freshness, step-up for role holders, and SSO across two applications
Sign in once and reach two applications, force a fresh sign-in with max_age, make a management role demand a second step, and roll out a required method with a deadline.
Partly readyUses your lab tenant
The lesson
Builds on: Security keys, passkeys, and biometrics, Codes, links, and approval prompts.
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.
- G20 `acr` / `acr_values` and acr-driven step-up
- G17 OIDC logout: RP-initiated, front-channel, back-channel, session management
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.
Reach lab-collage with the session from another application
Recorded as
oauth.authorizesucceeded (code_issued) forlab-collageabout[email protected].Force a fresh sign-in with max_age
Recorded as
oauth.authorizesucceeded (user_signed_inorsecond_step_required) forlab-collageabout[email protected].Give Ben the Help desk management role
Recorded as
tenant.users.management_roles.assignsucceeded about[email protected].Ben must set up a second step before finishing
Recorded as
oauth.authorizesucceeded (enrollment_required) about[email protected].Roll out a required method, then withdraw it
Recorded as
tenant.authentication.updatesucceeded.
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
In Authentication, check: second step
enrolled, role holders on, remember-device 0 days, Authenticator app Optional, Passkey or security key Optional.Ben should have only a password. If he still has an authenticator app from an earlier lab, use Users > Ben > Reset sign-in methods first.
In Roles, create
Help deskif it does not exist, withtenant.overview.read,tenant.users.readandtenant.users.unlock. Do not assign it yet.If
lab-collagedoes not exist, create it in OAuth > Clients with the web application preset: confidential, authorization code with PKCE, scopesopenid profile email, redirect URIhttp://127.0.0.1:8765/callback. You can also register the hosted callback pagehttps://beyondthelogin.dev/lab/callback/, which only displays the code.Load the client into your shell and define two helpers.
authorizeprints a fresh authorization URL and waits for the callback;redeemexchanges the code you paste.
export CLIENT_ID="<lab-collage client ID>"
read -rs CLIENT_SECRET
authorize() { # $1 = extra parameters, such as "&max_age=60"
eval "$(btl-lab pkce)"; eval "$(btl-lab state)"
echo "$ISSUER/oauth/authorize?response_type=code&client_id=$CLIENT_ID&redirect_uri=http://127.0.0.1:8765/callback&scope=openid%20profile%20email&state=$STATE&nonce=$NONCE&code_challenge=$CHALLENGE&code_challenge_method=S256$1"
btl-lab callback
}
redeem() {
read -rsp "Code: " CODE; echo
RESP=$(curl -s -u "$CLIENT_ID:$CLIENT_SECRET" "$ISSUER/oauth/token" -d grant_type=authorization_code \
-d "code=$CODE" -d redirect_uri=http://127.0.0.1:8765/callback -d "code_verifier=$VERIFIER")
echo "$RESP" | jq '{token_type, scope, expires_in, error}'
TOKEN=$(echo "$RESP" | jq -r '.access_token // empty'); ID_TOKEN=$(echo "$RESP" | jq -r '.id_token // empty')
}
Walkthrough
Single sign-on. In Ava's window, sign in through
$ISSUER/token-decoderand noteauth_timein its ID token. Then runauthorize, open the printed URL in the same window, approve consent if asked, and runredeem. No sign-in page appeared. Compare the two ID tokens withbtl-lab decode "$ID_TOKEN":auth_timeis identical,iatis not.
Why it matters: the identity provider reused its session for a second application. A new token's iat is when it was issued, not when Ava authenticated, which is why the lesson insists on auth_time.
Freshness on demand. Run
authorize "&max_age=60", wait two minutes, then open the URL. The tenant asks Ava to sign in again, and the new ID token'sauth_timeis a moment ago. With&max_age=0it asks every time.
Why it matters: this is reauthentication. The application states how recent the check must be, and the identity provider judges it from auth_time.
Stronger evidence for a sensitive role. In the portal open Users > Ben > Management roles and assign
Help desk. In Ben's window sign in through$ISSUER/token-decoder. After the password, the tenant requires him to set up a second step before finishing. Choose the authenticator app. The first ID token'samrhaspwdandotpbut nomfa. Sign out and in again: the second step is asked every time, andamrnow includesmfa.
Why it matters: the policy's scope is "users who hold management roles". The password that is enough for Ava is not enough for Ben any more, and a method set up during the same sign-in was not proved independently, so the first token does not claim MFA.
A forced migration. In Authentication set Passkey or security key to Required with a deadline three days ahead, and save. In Cora's window sign in with her password: before she can continue, the tenant sends her to set up a passkey. Register one, or a DevTools virtual passkey.
Why it matters: a new rule reaches people who already have accounts. The tenant's enrollment path and deadline keep it from blocking everyone on day one. After the deadline, a password alone is refused, and an administrator's method reset gives a person seven days to enroll.
Restore: set Passkey or security key back to Optional with no deadline, and save, before anyone else signs in. Ava, Ben and Mia would otherwise all be sent to enroll a passkey.
The application's side. Decode the
lab-collageID token from step 2 and write down the check a payroll-style export would make before acting:auth_timewithin the last 5 minutes, andamrcontainingmfa.
Why it matters: the application validates the trusted result against its own policy. A missing amr value is not proof of MFA, and a fresh iat is not proof of a fresh sign-in.
Identity provider session against application tokens. Sign Ava out with the sign-out button on
$ISSUER/account. Then call UserInfo with thelab-collageaccess token from step 2:
GET$ISSUER/oidc/userinfo
Open in console
GET $ISSUER/oidc/userinfo HTTP/1.1
Authorization: Bearer $TOKENIt still answers.
Why it matters: ending the identity provider's session does not end each application's own session or the tokens it holds, exactly as the lesson warns.
Break it
Repeat step 3 for Ben in a new private window after removing his authenticator app with Reset sign-in methods, and cancel at the enrollment page. No code is issued: Audit shows
oauth.authorizewith reasonenrollment_requiredand nocode_issuedafter it.
Why it matters: a sensitive role cannot be exercised with weaker evidence by walking away from the stronger check.
Run
authorize "&acr_values=urn:example:mfa"as Ava. The request succeeds and the ID token has noacrclaim. The parameter was ignored (G20).
Why it matters: an unsupported request parameter is not a guarantee. The application must check what came back, not what it asked for.
Check your work
Press Check my progress. The checks follow the single sign-on code, the fresh sign-in, Ben's role assignment, his required enrollment and the migration settings.
Also confirm by hand:
Two ID tokens with the same
auth_timefrom step 1, and one with a newerauth_timefrom step 2.UserInfo answered after Ava signed out.
Cleanup
Passkey or security key is Optional with no deadline, from the restore step.
Keep Ben's
Help deskrole; the authorization labs use it. Because he holds a role, his next sign-in asks him to set up an authenticator app again if Break it removed it. Keeplab-collage.
Missing infrastructure
**G20,
acrandacr_values.** With tenant-defined assurance levels, the full lab would requestacr_valuesfor a phishing-resistant level fromlab-collage, watch the tenant demand a passkey even from a fresh password session, and validate theacrclaim the application receives.G17, OpenID Connect logout. With RP-initiated and back-channel logout, step 6 would continue: end Ava's tenant session from
lab-collageand watch the application receive a logout token, so the gap the step shows can be closed on purpose.