ACR and AMR
This page helps you choose how strongly users sign in to your app, and check how they did.
Two claims in every ID token, and in every access token issued for a user, tell your app about the sign-in:
acr(Authentication Context Class Reference) is how strongly the user signed in. Goiabada has three levels, from a password alone to a password and a one-time code.amr(Authentication Methods References) is what the user did:pwdfor a password, andotpfor a one-time code from an authenticator app, which is what two-factor authentication adds.
Require a level
Section titled “Require a level”-
Set the least a sign-in to your app must reach. In the admin console, open your client, choose its Default ACR level on the Settings tab, and click Save. The field shows only while Authorization code with PKCE is on. See Clients.
-
To ask for more on one request, such as before a payment, add
acr_valuesto the authorization request:GET /auth/authorize?client_id=my-app&redirect_uri=https%3A%2F%2Fapp.example.com%2Fcallback&response_type=code&scope=openid&acr_values=urn%3Agoiabada%3Alevel2_mandatory&code_challenge=...&code_challenge_method=S256&state=... HTTP/1.1Host: auth.example.com -
Check
acrin the ID token before you allow the operation. It can be higher than you asked for, so compare it against what the operation needs rather than against youracr_values:{"acr": "urn:goiabada:level2_mandatory","amr": ["pwd", "otp"]}
Levels
Section titled “Levels”| Level | The user signs in with |
|---|---|
urn:goiabada:level1 |
A password |
urn:goiabada:level2_optional |
A password, plus a one-time code if they’ve set up two-factor authentication |
urn:goiabada:level2_mandatory |
A password and a one-time code, setting up two-factor authentication first if they haven’t |
They’re in that order, weakest first. A level satisfies itself and every level before it. Discovery lists the three in acr_values_supported.
Methods
Section titled “Methods”amr lists every method the user used, in the order they used them: ["pwd"], or ["pwd", "otp"]. Goiabada never sends mfa: look for otp in amr, or check acr.
Tokens from a refresh token carry the acr and amr of the sign-in they came from. Tokens from the password grant carry urn:goiabada:level1 and ["pwd"].
Which level a request asks for
Section titled “Which level a request asks for”The auth server takes the stronger of two levels: the client’s Default ACR level, and the first value in acr_values it recognizes. The client’s level is a floor, so acr_values=urn:goiabada:level1 on a client set to urn:goiabada:level2_mandatory still asks for a code.
A value is recognized only when it’s one of the three levels, exactly: level1 on its own isn’t. Values it doesn’t recognize are skipped, and when none is recognized the client’s level applies, with no error. The values are separated by single spaces; a parameter with any other spacing is ignored whole.
The level is fixed when the auth server accepts the request, so changing a client’s level doesn’t change sign-ins already under way.
A sign-in with no session
Section titled “A sign-in with no session”When the browser has no session with the auth server, the level and the user’s two-factor authentication decide what they’re asked for, and what your app gets:
| Level | User has two-factor authentication | The user enters | acr |
amr |
|---|---|---|---|---|
urn:goiabada:level1 |
No | A password | urn:goiabada:level1 |
["pwd"] |
urn:goiabada:level1 |
Yes | A password | urn:goiabada:level1 |
["pwd"] |
urn:goiabada:level2_optional |
No | A password | urn:goiabada:level2_optional |
["pwd"] |
urn:goiabada:level2_optional |
Yes | A password and a code | urn:goiabada:level2_optional |
["pwd", "otp"] |
urn:goiabada:level2_mandatory |
No | A password, then sets up two-factor authentication and enters a code | urn:goiabada:level2_mandatory |
["pwd", "otp"] |
urn:goiabada:level2_mandatory |
Yes | A password and a code | urn:goiabada:level2_mandatory |
["pwd", "otp"] |
urn:goiabada:level2_optional with only pwd is the level working as intended: the user hasn’t set up two-factor authentication. If your app wants to nudge them to, that’s the case to look for.
A session that’s already there
Section titled “A session that’s already there”A user who already signed in has a session, and the session remembers the level it reached. Only a session of the user who’s signing in counts: a session the browser holds for someone else counts as none.
- The session’s level is at least the one asked for. The user isn’t asked for anything, and your app gets the session’s level, which can be higher than the one asked for.
- It’s lower. That’s a step-up. The user isn’t asked for their password again, only for what the new level adds, as in the table above: usually a one-time code. The session moves up to the new level, and reusing a session never lowers its level. When the user enters a code,
auth_timebecomes the time they entered it.
A request with prompt=login doesn’t count the session at all. The user signs in as if there were none, entering every factor the level needs, as in the table above, and the session then takes that sign-in’s level, methods and time. That can lower it: prompt=login at a level 1 client over a session at urn:goiabada:level2_mandatory leaves the session at urn:goiabada:level1 with ["pwd"], so the next level 3 request asks for the code again. acr, amr and auth_time then always describe the same sign-in.
When a user sets up or removes two-factor authentication, each of their sessions is checked again for a second factor before it’s used for a level above urn:goiabada:level1: a user with an authenticator enters a code, and a user with none left is asked for nothing at urn:goiabada:level2_optional and sets a new one up at urn:goiabada:level2_mandatory. That check stays owed until a sign-in completes it: a sign-in abandoned at the code asks again next time. A silent request (prompt=none) gets interaction_required instead.
Removing the authenticator also lowers each of the user’s sessions to what a password alone reaches: urn:goiabada:level2_optional at most, and ["pwd"]. Its auth_time goes back to when the password was entered, because after a code auth_time is the code’s time, and ["pwd"] beside it would say a password was entered then. Nobody is signed out. Every token issued from those sessions afterwards says so, at any level: a level 3 request is a step-up, and the user sets up a new authenticator. What was issued before the removal keeps its claims: tokens, and the refresh tokens that renew them, say what the sign-in they came from said. Ending the user’s sessions revokes those refresh tokens. A sign-in under way when the authenticator is removed starts again from the password, even if the user has set up another one meanwhile: its code came from the one removed.
acr and max_age are separate checks. A session at a high enough level still asks for a sign-in when the user signed in longer ago than max_age allows. See Sessions.
Setting up two-factor authentication during a sign-in
Section titled “Setting up two-factor authentication during a sign-in”At urn:goiabada:level2_mandatory, a user with no authenticator sets one up before they enter a code. The new authenticator is stored only while the account still has none. If it gains one meanwhile, because the same user finished setting one up in another tab or on their account page, nothing is stored, and the sign-in ends on a page saying “Your two-factor authentication settings changed”. That sign-in can’t go on: the user goes back to the app and signs in again, which starts from the account’s current settings.