RFC 9470 Step-Up Authentication Challenge — the conn-free core primitive.
A resource server that needs a fresher or stronger end-user authentication for
a sensitive operation evaluates the presented (already verified) access token
against an Attesto.StepUp.Requirement and, when it is not satisfied, answers
RFC 9470 §3 WWW-Authenticate: Bearer error="insufficient_user_authentication"
with the acr_values / max_age the client must re-request at the
authorization endpoint.
This module is conn-free: it reads only the token's acr / auth_time claims
and a caller-supplied now, decides satisfaction (the framing layer renders
the challenge), and never touches a Plug.Conn, a store, or a wall clock.
Semantics
- The two dimensions are a conjunction (RFC 9470 §4): the token must
satisfy the
acrset membership AND theauth_timefreshness bound. - Fail-closed: a token whose
acris absent/non-string (when anacr_valuesset is required), or whoseauth_timeis absent/non-integer (when amax_ageis required), does NOT satisfy the requirement. A token from a non-OIDC grant (noauth_time) therefore always challenges a freshness requirement — step-up routes are for end-user grants.
Summary
Types
The RFC 9470 §3 challenge parameters naming what the client must re-request.
Functions
The RFC 9470 §3 challenge parameters for requirement: :acr_values
(space-delimited) and/or :max_age. Transport-free — the plug owns the
WWW-Authenticate scheme (Bearer vs DPoP) and quoting.
Evaluate verified token claims against requirement at now (unix seconds).
Whether the token's acr / auth_time claims satisfy requirement at now
(the conjunction of both dimensions; fail-closed on absent/malformed claims).
Types
@type challenge() :: %{ optional(:acr_values) => String.t(), optional(:max_age) => non_neg_integer() }
The RFC 9470 §3 challenge parameters naming what the client must re-request.
Functions
@spec challenge_params(Attesto.StepUp.Requirement.t()) :: challenge()
The RFC 9470 §3 challenge parameters for requirement: :acr_values
(space-delimited) and/or :max_age. Transport-free — the plug owns the
WWW-Authenticate scheme (Bearer vs DPoP) and quoting.
@spec evaluate(Attesto.StepUp.Requirement.t(), map(), integer()) :: :ok | {:error, :insufficient_user_authentication, challenge()}
Evaluate verified token claims against requirement at now (unix seconds).
Returns :ok, or {:error, :insufficient_user_authentication, challenge} where
challenge carries the acr_values (space-delimited) / max_age the client
must re-request.
@spec satisfied?(Attesto.StepUp.Requirement.t(), map(), integer()) :: boolean()
Whether the token's acr / auth_time claims satisfy requirement at now
(the conjunction of both dimensions; fail-closed on absent/malformed claims).