flashman
← All posts

OAuth step-up acr and auth_time debugging

Debug OAuth step-up 401s by reading acr and auth_time, then requesting a fresh token instead of retrying one the resource server has already rejected.

2026-10-01 · 6 min read · Rahul Chitturi

  • oauth
  • jwt
  • authentication

A resource server can accept a token's signature and scope and still return 401 because the user authentication behind that token is too weak or too old. OAuth step-up, defined in RFC 9470, uses the error insufficient_user_authentication for that case.

Retrying the same bearer token repeats the 401. The client has to request a new token after the user authenticates again at the level the resource server asked for.

Separate this 401 from scope and signature failures

Read the WWW-Authenticate error before refreshing secrets or widening scopes. insufficient_scope means the token is missing an authorization grant. invalid_token means the token itself is unacceptable. insufficient_user_authentication means the grant may be fine and the authentication event is not.

The challenge can include acr_values and max_age. acr_values lists authentication context classes the resource will accept. max_age is the maximum age, in seconds, of the user authentication. max_age of zero asks for a fresh login.

  • Record the HTTP status and the error parameter.
  • Copy acr_values and max_age from the challenge.
  • Decode a synthetic token of the same shape in a local tool.
  • Do not log the access token or the Authorization header.

Read acr and auth_time as documented claims

acr is a string chosen by the authorization server, not a number you can sort. A value of 2 is not automatically stronger than loa-high unless your identity provider documents that ordering. A missing acr means the server made no class claim. It does not mean the highest class.

auth_time is a NumericDate in seconds. A client that treats it as milliseconds will think a fresh login is decades old. A refresh token often preserves the original auth_time, so a refreshed access token can fail max_age immediately.

Request a new authentication, then compare fixtures

Send the challenged acr_values and max_age on a new authorization request. When max_age is zero, the authorization server should prompt the user again. The next access token should carry an acr the API accepts and an auth_time inside the window.

Use Flashman's JWT decoder on synthetic tokens, the JSON formatter for the WWW-Authenticate parameters you captured as JSON, the timestamp converter for auth_time, and diff to compare the rejected token's claims with the token issued after step-up.

Try these tools