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

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.

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. Reach lab-collage with the session from another application

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

  2. Force a fresh sign-in with max_age

    Recorded as oauth.authorize succeeded (user_signed_in or second_step_required) for lab-collage about [email protected].

  3. Give Ben the Help desk management role

    Recorded as tenant.users.management_roles.assign succeeded about [email protected].

  4. Ben must set up a second step before finishing

    Recorded as oauth.authorize succeeded (enrollment_required) about [email protected].

  5. Roll out a required method, then withdraw it

    Recorded as tenant.authentication.update succeeded.

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

  1. In Authentication, check: second step enrolled, role holders on, remember-device 0 days, Authenticator app Optional, Passkey or security key Optional.

  2. 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.

  3. In Roles, create Help desk if it does not exist, with tenant.overview.read, tenant.users.read and tenant.users.unlock. Do not assign it yet.

  4. If lab-collage does not exist, create it in OAuth > Clients with the web application preset: confidential, authorization code with PKCE, scopes openid profile email, redirect URI http://127.0.0.1:8765/callback. You can also register the hosted callback page https://beyondthelogin.dev/lab/callback/, which only displays the code.

  5. Load the client into your shell and define two helpers. authorize prints a fresh authorization URL and waits for the callback; redeem exchanges 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

  1. Single sign-on. In Ava's window, sign in through $ISSUER/token-decoder and note auth_time in its ID token. Then run authorize, open the printed URL in the same window, approve consent if asked, and run redeem. No sign-in page appeared. Compare the two ID tokens with btl-lab decode "$ID_TOKEN": auth_time is identical, iat is 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.

  1. 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's auth_time is a moment ago. With &max_age=0 it 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.

  1. 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's amr has pwd and otp but no mfa. Sign out and in again: the second step is asked every time, and amr now includes mfa.

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.

  1. 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.

  1. The application's side. Decode the lab-collage ID token from step 2 and write down the check a payroll-style export would make before acting: auth_time within the last 5 minutes, and amr containing mfa.

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.

  1. Identity provider session against application tokens. Sign Ava out with the sign-out button on $ISSUER/account. Then call UserInfo with the lab-collage access token from step 2:

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

It 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

  1. 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.authorize with reason enrollment_required and no code_issued after it.

Why it matters: a sensitive role cannot be exercised with weaker evidence by walking away from the stronger check.

  1. Run authorize "&acr_values=urn:example:mfa" as Ava. The request succeeds and the ID token has no acr claim. 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_time from step 1, and one with a newer auth_time from 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 desk role; 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. Keep lab-collage.

Missing infrastructure

  • **G20, acr and acr_values.** With tenant-defined assurance levels, the full lab would request acr_values for a phishing-resistant level from lab-collage, watch the tenant demand a passkey even from a fresh password session, and validate the acr claim 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-collage and watch the application receive a logout token, so the gap the step shows can be closed on purpose.

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