A validated CIBA backchannel authentication request (CIBA Core §7.1).
validate/3 runs the request-shape validation of the backchannel
authentication endpoint on the authenticated client's wire parameters:
the scope rules, the exactly-one-hint rule, the delivery-mode-dependent
client_notification_token requirement, binding_message / user_code /
requested_expiry shape checks, and - when the client sends a signed
authentication request (§7.1.1) - JWT verification against the client's
registered JWKS via Attesto.RequestObject.
It is deliberately conn-free and registry-free: client authentication
(invalid_client) and the client's registered CIBA metadata are the host's
concern; the host passes the latter in as the client map. What this module
can NOT decide is anything requiring the resolved end-user - hint resolution
(unknown_user_id, expired_login_hint_token) and user-code verification
(missing_user_code, invalid_user_code) happen in the host between
validate/3 and Attesto.CIBA.issue/4.
Signed authentication requests (§7.1.1, FAPI-CIBA §5.2.2)
When params carries a request JWT it MUST be the only authentication
request parameter - the spec forbids parameters outside the JWT, so any
other key present alongside request is rejected. (Client-authentication
parameters such as client_assertion are allowed on the wire; the caller
strips them before calling validate/3.) The JWT is verified with the
client's registered JWKS and must carry iss (the client_id), aud (the
OP's Issuer Identifier, passed as :issuer), exp, iat, nbf, and
jti - all REQUIRED by §7.1.1. FAPI-CIBA additionally requires that ALL
requests be signed (require_signed_request: true) and bounds the
nbf→exp lifetime to 60 minutes (the :max_request_lifetime_seconds
default).
Each known CIBA authentication-request parameter carried in the signed JWT
(scope, the hints, binding_message, user_code, acr_values,
client_notification_token) MUST be a JSON string per §7.1.1; a non-string
value is rejected as invalid_request rather than coerced. requested_expiry
may be a string or a number.
Replay defense (FAPI-CIBA §5.2.2)
A verified signed request exposes its jti and exp on the returned struct
as request_jti / request_exp (both nil for an unsigned request). The
core does NOT track jti - it is stateless by design - so the host MUST
record each signed request's request_jti for the request's lifetime (until
request_exp) and reject a repeat at the validate/3 boundary. Without
this, a captured signed authentication request can be replayed to start
duplicate CIBA transactions within its lifetime.
Summary
Types
The authenticated client's registered CIBA metadata (CIBA Core §4)
Which of the three CIBA §7.1 hints the client sent, and its value.
Functions
Validate the wire parameters of a backchannel authentication request for an authenticated client (CIBA Core §7.1 / §7.1.1).
Types
@type client() :: %{ :client_id => String.t(), :token_delivery_mode => delivery_mode(), optional(:jwks) => map() | [map()] | nil, optional(:request_signing_alg) => String.t() | nil, optional(:user_code_parameter) => boolean() }
The authenticated client's registered CIBA metadata (CIBA Core §4):
:client_id(required) - the authenticated client.:token_delivery_mode(required) -:poll|:ping|:push, the registeredbackchannel_token_delivery_mode. A client without one is not registered for CIBA (unauthorized_client). Note FAPI-CIBA forbids:push.:jwks- the client's registered public keys, required to accept a signed authentication request.:request_signing_alg- the registeredbackchannel_authentication_request_signing_alg; when set, a signed request must use exactly this algorithm.:user_code_parameter- the registeredbackchannel_user_code_parameter(defaultfalse).
@type delivery_mode() :: :poll | :ping | :push
@type error() ::
:invalid_request
| :invalid_scope
| :invalid_binding_message
| :unauthorized_client
@type hint() :: {:login_hint | :login_hint_token | :id_token_hint, String.t()}
Which of the three CIBA §7.1 hints the client sent, and its value.
@type t() :: %Attesto.CIBA.Request{ acr_values: [String.t()], binding_message: String.t() | nil, client_id: String.t(), client_notification_token: String.t() | nil, delivery_mode: delivery_mode(), hint: hint(), request_exp: non_neg_integer() | nil, request_jti: String.t() | nil, requested_expiry: pos_integer() | nil, scope: [String.t()], signed?: boolean(), user_code: String.t() | nil }
Functions
Validate the wire parameters of a backchannel authentication request for an authenticated client (CIBA Core §7.1 / §7.1.1).
params is the string-keyed parameter map with any client-authentication
parameters (client_id, client_assertion, ...) already stripped by the
caller. Options:
:issuer- the OP's Issuer Identifier, the requiredaudof a signed authentication request. Required to accept signed requests.:require_signed_request- whentrue, reject a plain-parameter request (FAPI-CIBA §5.2.2 requires signed requests). Defaultfalse.:accepted_algs- JOSE algorithms acceptable for signed requests. Defaults toAttesto.SigningAlg.default_client_algs/0; a FAPI-CIBA deployment narrows this to["PS256", "ES256"]. The client's registered:request_signing_alg, when set, must be inside this set and becomes the only accepted algorithm.:max_request_lifetime_seconds- bound on a signed request'snbf→explifetime. Default3600(FAPI-CIBA §5.2.2's 60 minutes).:user_code_supported- whether this OP advertisesbackchannel_user_code_parameter_supported. Auser_codesent when the OP or the client's registration does not support it is rejected. Defaultfalse.:binding_message_max_length- maximumbinding_messagelength in graphemes. Default128.:require_binding_message- whentrue, a request without abinding_messageis rejected withinvalid_binding_message(FAPI-CIBA §5.2.2 requires one when no other unique authorization context exists). Defaultfalse.:min_client_notification_token_length- entropy floor for ping/pushclient_notification_tokens, as a length lower bound. Default22(128 bits base64-encoded).:now- unix seconds orDateTime, for signed-request time checks.
Returns {:ok, %Attesto.CIBA.Request{}} or {:error, reason} where
reason is the CIBA §13 error code the endpoint renders: :invalid_request
(malformed/missing parameters, or a signed request that fails verification),
:invalid_scope, :invalid_binding_message, or :unauthorized_client (the
client is not registered for CIBA).