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

Following a step-up challenge

At 09:02 UTC you open the editor's tidy-up view and choose Delete 38 photos. The editor holds the access token it obtained this morning by refreshing, which still reflects yesterday's password sign-in, and it starts with the first photo on the list.

Receiving the challenge

All domains, codes, and tokens in these examples are fictional. The editor sends the deletion with its current token:

DELETE /photos/9137 HTTP/1.1
Host: api.photos.example
Authorization: Bearer demo-access-token-31

The photo API validates the token as usual. Its signature, issuer, audience, and expiry are fine, and it carries photos.delete. Then the API applies its policy for deletions: acr must be urn:example:photos:acr:phishing-resistant, and auth_time must be no more than 300 seconds ago. The token says basic, and its sign-in happened 86,520 seconds ago. The API refuses:

HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="insufficient_user_authentication",
  error_description="A recent phishing-resistant sign-in is required",
  acr_values="urn:example:photos:acr:phishing-resistant",
  max_age="300"

The header is split across lines for display. error names the problem: the authentication behind this token does not meet the API's requirements. acr_values lists the acceptable authentication context classes, separated by spaces and in order of preference, and the API will accept any one of them. max_age is the number of seconds allowed since your last active authentication: the last time you responded to a sign-in prompt at the authorization server. A session that quietly continues does not count. A challenge can carry either requirement or both, and if the token also lacked a scope, it could include a scope attribute as well.

The response uses status 401, as an invalid token would, because the remedy is a different token. The error_description is optional text meant for developers. As with other error descriptions, the editor does not insert it into its page as markup.

Asking for a new sign-in

The editor remembers what it was doing, the 38 photos waiting to be deleted, and tells you that deleting photos needs a fresh sign-in with your passkey. When you choose to continue, it builds a new authorization request, copying the two requirements from the challenge exactly as it received them:

https://auth.photos.example/authorize
  ?response_type=code
  &client_id=photo-editor
  &redirect_uri=https%3A%2F%2Feditor.example%2Foauth%2Fcallback
  &scope=photos.read%20photos.delete
  &acr_values=urn%3Aexample%3Aphotos%3Aacr%3Aphishing-resistant
  &max_age=300
  &state=demo-attempt-31
  &code_challenge=gmBRnRQIy0EQKWg3CemaN64l-xOox_nHZAphyPcG6hI
  &code_challenge_method=S256

Everything else is an ordinary authorization request, with a fresh state and PKCE challenge for this attempt. The requirements are the only new part.

At the photo service, you still have yesterday's session. On an ordinary visit, the server would recognize you and continue without a prompt. This time, max_age=300 tells it that a sign-in more than five minutes old is too old, so it must ask you to authenticate again. acr_values tells it which kind of sign-in to ask for, so it prompts for your passkey rather than your password. You use your passkey at 09:03:00.

Under the step-up specification, the authorization server should treat the requested acr as a requirement, not a preference. It puts that value in the new access token only when the sign-in actually met it. If the sign-in falls short, the request fails instead of producing a token the API would refuse.

Your browser returns to https://editor.example/oauth/callback with a code, the attempt's state, and the iss parameter. The editor checks them as it would for any callback, then exchanges the code with its PKCE verifier. It is a public client, so it sends its client ID and no client credential.

Retrying with the new token

The new access token, decoded, describes this morning's passkey sign-in:

{
  "iss": "https://auth.photos.example",
  "sub": "user-2048",
  "aud": "https://api.photos.example",
  "client_id": "photo-editor",
  "scope": "photos.read photos.delete",
  "iat": 1790845385,
  "exp": 1790845985,
  "jti": "demo-token-id-32",
  "auth_time": 1790845380,
  "acr": "urn:example:photos:acr:phishing-resistant"
}

auth_time is 09:03:00, and the token was issued five seconds later. The editor does not decode the token to confirm any of this. It repeats the request it was trying to make and lets the API decide:

DELETE /photos/9137 HTTP/1.1
Host: api.photos.example
Authorization: Bearer demo-access-token-32
HTTP/1.1 204 No Content

The API runs the same checks as before. At 09:03:10, the sign-in is 10 seconds old, well inside the 300 allowed, and its acr is one the API accepts. The photo is deleted, and the editor continues with the other 37.

The photo editor sends a deletion to the photo API with access token 31. The token is valid and has the delete scope, but its sign-in was a password sign-in from yesterday, so the API answers 401 with insufficient_user_authentication, the phishing-resistant acr value, and max_age 300. The editor sends you to the authorization server with acr_values and max_age. The server asks for your passkey because your session is too old, then returns a code. The editor exchanges the code with its PKCE verifier and receives access token 32, whose acr is phishing-resistant and whose auth_time is the new sign-in. The editor repeats the deletion with token 32, and the API accepts it. The photo editor sends a deletion to the photo API with access token 31. The token is valid and has the delete scope, but its sign-in was a password sign-in from yesterday, so the API answers 401 with insufficient_user_authentication, the phishing-resistant acr value, and max_age 300. The editor sends you to the authorization server with acr_values and max_age. The server asks for your passkey because your session is too old, then returns a code. The editor exchanges the code with its PKCE verifier and receives access token 32, whose acr is phishing-resistant and whose auth_time is the new sign-in. The editor repeats the deletion with token 32, and the API accepts it.
The API states its requirement, the authorization server meets it with a new sign-in, and the editor retries with the new token. The API makes the final decision both times.

The new sign-in will not stay fresh. At 09:08:00 it turns five minutes old, and from then on this token no longer meets the deletion policy, although it still works for browsing until it expires. If the tidy-up were still running then, the API would challenge again, and the editor would have to decide whether to ask you for another sign-in or stop.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2The photo API answers 401 with error="insufficient_user_authentication" and max_age="300". What should the editor do?

QUESTION 2 OF 2You still have yesterday's session at the photo service. Why does the server ask you to sign in again when the request includes max_age=300?

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

Learn identity