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

OPENID CONNECT · LAB

Read amr from your tenant's authentication policy

Sign in four different ways and read what amr says for each, see acr_values ignored without an error, and write a relying-party rule that treats a missing acr as falling short.

Partly readyUses your lab tenant

The lesson

Builds on: Connecting a sign-in to an account.

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. Turn on the authenticator app and passkeys

    Recorded as tenant.authentication.update succeeded.

  2. Enroll an authenticator app for Ava

    Recorded as account.security succeeded (method_enrolled) about [email protected].

  3. Reach the second step after the password

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

  4. Complete the second step with an app code

    Recorded as account.second_step succeeded (second_step_completed) about [email protected].

  5. Complete it with a recovery code instead

    Recorded as account.second_step succeeded (recovery_code_used) about [email protected].

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

amr in your tenant's ID tokens follows its real authentication policy. Classes of authentication (acr) are not available yet (G20), so the context half of the lesson is practised on the relying-party side.

  1. Press Start. In Lab Photos, open Authentication and note the current values. Set the authenticator app (TOTP) to Optional, passkeys to Optional with passwordless sign-in allowed and user verification required, and the second step to ask users who have enrolled one. Keep "remember this browser" at 0 days so every sign-in asks again.

  2. Have an authenticator app and a device that can hold a passkey ready.

  3. source ~/btl-oidc.sh and run btl-lab callback before each request.

Walkthrough

  1. Read what the provider publishes.

GET$ISSUER/.well-known/openid-configuration Open in console
GET $ISSUER/.well-known/openid-configuration

There is no acr_values_supported, claims_parameter_supported is false, and amr is among claims_supported.

Why it matters: a relying party can ask only for classes a provider publishes, and the meaning of each class comes from the provider's own documentation.

  1. Password only. Sign in as Ava with signin prompt=login, redeem, and read both claims:

part "$ID_TOKEN" | jq '{amr, acr}'

{"amr":["pwd"],"acr":null}.

Why it matters: amr lists the methods used in this sign-in, and acr is simply absent.

  1. Password and app. At $ISSUER/account/security, set up the authenticator app for Ava and save her recovery codes somewhere safe. Run signin prompt=login again: password, then a code. amr is ["pwd","otp","mfa"].

Why it matters: pwd and otp name the two methods, and mfa says they counted as more than one factor.

  1. Password and a recovery code. Repeat step 3, but at the second step choose a recovery code instead of the app. amr is again ["pwd","otp","mfa"].

Why it matters: two different methods produce the same list. That is one reason the lesson keeps access decisions off amr.

  1. A passkey. At $ISSUER/account/security, add a passkey. Run signin prompt=login and choose to sign in with a passkey from the sign-in page. A synced passkey gives ["swk","mfa"], a device-bound security key ["hwk","mfa"].

Why it matters: providers describe passkeys differently, and neither value says "phishing-resistant" by itself. That judgment belongs in a class definition.

  1. Ask for a class anyway.

signin prompt=login acr_values=urn%3Alab%3Aacr%3Aphishing-resistant

Sign in with the password and app code. There is no error, a normal code comes back, and the ID token has no acr.

Why it matters: the specification's minimum for acr_values is "no error", so every response needs checking.

  1. Write the portal rule from the lesson: an allowlist of acr values per action, where a missing acr meets no requirement, and amr is used only for a readable sign-in history line.

allowed() { part "$ID_TOKEN" | jq -r --arg action "$1" '
  {"ordinary": ["urn:lab:acr:mfa", "urn:lab:acr:phishing-resistant"], "raw_download": ["urn:lab:acr:phishing-resistant"]} as $rules
  | if (.acr != null and ($rules[$action] | index(.acr))) then "allow \($action)" else "refuse \($action): acr \(.acr // "missing")" end'; }
history_line() { part "$ID_TOKEN" | jq -r '[.amr[]? | {"pwd":"password","otp":"one-time code","swk":"passkey","hwk":"security key","mfa":null}[.] // empty] | join(" and ")'; }
allowed raw_download; history_line

Run it after each kind of sign-in from steps 2 to 6. Every token is refused for raw_download, and the history line reads, for example, "password and one-time code".

Why it matters: a class is defined by the provider's published meaning, and a method list is display and audit material, not an access decision.

Break it

  1. "No claim means MFA". Try to remove amr from ID tokens: in OAuth > ID token managers, open the manager assigned to lab-collage and look for an override for amr. There is none to choose, because amr and auth_time are protected claims the tenant always derives itself. Now test a careless relying-party rule against a claims set without amr, the shape another provider might send:

echo '{"sub":"someone","auth_time":1790929800}' | jq -r 'if (.amr == null or (.amr | index("mfa"))) then "treated as MFA" else "not MFA" end'

It prints treated as MFA for a sign-in that proved nothing. Fix the rule so a missing claim is never evidence of one factor or two.

Restore: keep only the corrected rule in your notes and code.

Check your work

Press Check my progress. The checks look for the policy change, Ava's authenticator app enrollment, a sign-in that reached the second step, and second steps completed with an app code and with a recovery code.

Passkey enrollments appear in Audit as account.security method_enrolled too, and a passkey sign-in records account.sign_in followed by oauth.authorize code_issued, with no second step.

Cleanup

  1. Restore the authentication values you noted in Setup.

  2. Keep Ava's authenticator app and passkey. Step up a role holder uses the app.

  3. One recovery code was used, so generate new ones at $ISSUER/account/security.

Missing infrastructure

  • G20 (acr and acr_values). Tenant-defined authentication context classes. The full lab would define urn:lab:acr:mfa and urn:lab:acr:phishing-resistant in Authentication, mapped to method rules, see them in acr_values_supported, set default_acr_values on lab-collage, request acr_values, and read acr beside amr in the ID token. Step 7's rule would then allow the passkey sign-in.

  • G19 (claims request parameter). Requesting acr as an essential claim with a values list, to compare voluntary and essential behavior.

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