skip to content

RFC 9470 lets an API refuse a call whose access token reflects too weak a sign-in: what does the resource server return, and what does the client do next?

level: seniorimportance: should knowfreq 35%

answer

  1. the token is fine, the sign-in is not
  2. a challenge, not a redirect
  3. 401, not 403
  4. same two parameter names on both legs
  5. the protocol sets no elevation window

basics

~20 s

The resource server answers 401 Unauthorized with WWW-Authenticate: Bearer error="insufficient_user_authentication", optionally carrying acr_values and max_age to state what it needs. The client then starts a new authorization request with those values, and the provider re-authenticates.

solid answer

~50 s

RFC 9470 defines the challenge for a resource server that accepts an access token as valid but finds the **authentication behind it** too weak or too old. It answers `401 Unauthorized` with `WWW-Authenticate: Bearer error="insufficient_user_authentication"`, and may carry `acr_values`, `max_age`, or both, to state exactly what would satisfy it. The client reads the challenge, starts a new authorization request carrying those same values, the provider re-authenticates the user accordingly, and the call is retried with the newly issued access token. Note which step-up this is: an *authentication* step-up says the person's sign-in was insufficient and uses 401, whereas a token that simply lacks a permission is a scope shortfall, a different error code with a 403. Nothing in the challenge says how long the elevated state lasts or which operations should demand it — those stay the application's own policy.

code

http · 4 lines
http
HTTP/1.1 401 Unauthorized
WWW-Authenticate: Bearer error="insufficient_user_authentication",
  error_description="A more recent authentication is required",
  max_age="300"

go deeper

for a junior

Recall that an API can reject a call not because the credential is bad but because the user's sign-in was too weak or too old, and that it says so with a challenge rather than a redirect.

for a middle

Explain the exact response — 401 with a bearer challenge carrying the insufficient-authentication error code and optionally the context and age parameters — and why the client answers it with a new authorization request.

for a senior

Show the whole round trip under failure: the loop when the provider cannot produce the requested context, the refresh-and-retry dead end, and how you would check what the provider actually advertises before requiring a value.

for a principal

Decide where this belongs across services: whether each API states its own requirement or a shared policy layer does, and what it costs a client estate to implement a challenge that some of its clients cannot act on.

## The gap this closes A relying party can ask a provider for a stronger sign-in on the authentication request. But the party that usually *discovers* the need is not the relying party — it is the API being called. A dental laboratory's case-tracking site calls its own case service to release a patient's record; the service is the one holding the rule that this operation needs more than an ordinary sign-in. Before RFC 9470 there was no standard way for it to say so, and deployments invented private error bodies that no general-purpose client understood. ## The challenge The access token itself is fine — not expired, not malformed, correctly signed, carrying the right permission. What is insufficient is the **authentication that stands behind it**. The resource server answers: - status **`401 Unauthorized`**; - a **`WWW-Authenticate: Bearer`** challenge; - the error code **`insufficient_user_authentication`**; - optionally **`acr_values`**, **`max_age`**, or both, as auth-params stating what would satisfy it. Those two auth-params are deliberately the same names the authentication request uses, so the client can copy the requirement across without interpreting it. A challenge that carries neither says only "stronger or fresher", which leaves the client guessing; a useful one states the requirement. ## The round trip 1. The client calls the API with an access token in an `Authorization: Bearer` header. 2. The resource server compares the authentication context behind that token against the operation's requirement, and finds it short. 3. It returns the `401 Unauthorized` challenge above, naming `acr_values` and/or `max_age`. 4. The client starts a **new authorization request** at the provider carrying those values. 5. The provider re-authenticates the user as asked, and a new access token is issued. 6. The client retries the original call with the new token. Step 4 is where the two halves of this subject meet: the resource server states a requirement in the vocabulary of the challenge, and the client restates it in the vocabulary of the authorization request. They are the same two words on purpose. ## Which step-up this is Two different shortfalls are both called "step-up", and confusing them sends an engineer to fix the wrong thing. | | Authentication step-up | Scope shortfall | |---|---|---| | What is insufficient | how the user signed in, or how long ago | what the token is permitted to do | | Status | `401 Unauthorized` | 403 | | Bearer error code | `insufficient_user_authentication` | `insufficient_scope` | | What fixes it | a new authorization request asking for a stronger or fresher sign-in | obtaining a token with the permission the operation needs | | Who must act | the end user, by authenticating again | the client, by asking for a wider grant | The status codes are the quickest tell: 401 says the credential does not establish enough about **who** is there; 403 says the identity is established and is not permitted to do **this**. ## What the resource server must already know The challenge is only possible if the resource server can tell a strong, recent authentication from a weak, stale one for the token in front of it. That information reaches it either as claims it can read directly, or by asking the authorization server about the token — the mechanism is outside this subject, but the dependency is not: **a resource server with no view of the authentication context cannot raise this challenge at all**, and will fall back to refusing the call with no actionable reason. ## What the protocol deliberately does not decide Three things people expect to find here are not in it: - **Which operations require elevation.** That is a product decision about risk, kept by the service that owns the operation. - **How long an elevation lasts.** The challenge speaks about one call. Whether the next call ten minutes later is waved through is the application's own policy and its own record-keeping. - **What the context strings mean.** The challenge names values; the grading of what an assurance level demands comes from a profile agreed elsewhere. ## Failure modes worth naming - **The loop.** The client repeats the authorization request, the provider re-authenticates with a method that still does not satisfy the requirement, and the API challenges again. The cause is usually that the provider cannot produce the requested context at all — check what it advertises as supported before requiring it. - **The silent downgrade.** The client re-authenticates, receives a new token, retries, and succeeds — because the resource server re-read a cached decision rather than the new authentication context. The check must run against the token actually presented. - **The wrong fix.** A team reads 401 as "token expired", refreshes, retries with an equivalent token and sees the same challenge. A refresh does not change how the user authenticated.

  • How does this challenge differ from one about a missing permission?
    An authentication step-up says the sign-in behind the token was too weak or too old: `401 Unauthorized` with `insufficient_user_authentication`, fixed by authenticating the user again. A scope shortfall says the token is not permitted to perform the operation: a 403 with `insufficient_scope`, fixed by obtaining a wider grant. Different party at fault, different remedy.
  • How does the resource server know the authentication context behind the access token?
    Either the context is available to it as claims it can read for that token, or it asks the authorization server about the token. Which route is used is an integration decision. What matters is the dependency: without a view of how and when the user authenticated, the resource server cannot distinguish a strong sign-in from a weak one and cannot raise this challenge.
  • Does the challenge say how long the elevated state lasts?
    No. It states a requirement for the call being refused, and nothing more. How long an elevated authentication is honoured afterwards, which operations demand it, and where that is recorded are the application's own policy decisions — the protocol provides the vocabulary for the ask and the challenge, not the window.
  • What does a challenge that carries neither auth-param leave the client to do?
    Guess. Both are optional, so a bare `insufficient_user_authentication` says only that the authentication was insufficient without saying what would suffice. A client can retry with its strongest configured request, but a resource server that states the requirement turns one wasted round trip into none.

A courier is turned away at a laboratory's door not because the parcel is wrong but because the authorisation on file is too old; the doorkeeper names which authorisation will do, and the courier comes back with that one rather than knocking harder with the same paperwork.

saying these in an interview costs you the question

  • Reads the 401 as an expired or invalid access token
  • Refreshes the access token and retries the same call
  • Confuses this with a 403 about a missing permission
  • Thinks the challenge defines how long elevation lasts
  • Assumes any resource server can see the authentication context
  • Believes the challenge itself re-authenticates the user