# `Attesto.CIBA.Request`
[🔗](https://github.com/XukuLLC/attesto/blob/v1.3.0/lib/attesto/ciba/request.ex#L1)

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.

# `client`

```elixir
@type client() :: %{
  :client_id =&gt; String.t(),
  :token_delivery_mode =&gt; delivery_mode(),
  optional(:jwks) =&gt; map() | [map()] | nil,
  optional(:request_signing_alg) =&gt; String.t() | nil,
  optional(:user_code_parameter) =&gt; 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
    registered `backchannel_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 registered
    `backchannel_authentication_request_signing_alg`; when set, a signed
    request must use exactly this algorithm.
  * `:user_code_parameter` - the registered
    `backchannel_user_code_parameter` (default `false`).

# `delivery_mode`

```elixir
@type delivery_mode() :: :poll | :ping | :push
```

# `error`

```elixir
@type error() ::
  :invalid_request
  | :invalid_scope
  | :invalid_binding_message
  | :unauthorized_client
```

# `hint`

```elixir
@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.

# `t`

```elixir
@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
}
```

# `validate`

```elixir
@spec validate(client(), map(), keyword()) :: {:ok, t()} | {:error, error()}
```

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 required `aud` of a signed
    authentication request. Required to accept signed requests.
  * `:require_signed_request` - when `true`, reject a plain-parameter
    request (FAPI-CIBA §5.2.2 requires signed requests). Default `false`.
  * `:accepted_algs` - JOSE algorithms acceptable for signed requests.
    Defaults to `Attesto.SigningAlg.default_client_algs/0`, including legacy
    EdDSA only over Ed25519 and RFC 9864 Ed25519, never Ed448. Supplying a
    list selects an explicit non-FAPI algorithm policy unless
    `:enforce_fapi_alg_policy` is also `true`. The client's registered
    `:request_signing_alg`, when set, must be inside this set and becomes the
    only accepted algorithm.
  * `:enforce_fapi_alg_policy` - enforce the FAPI RSA modulus and Edwards
    curve restrictions in addition to `:accepted_algs`. Defaults to `true`
    when `:accepted_algs` is omitted and `false` when the caller supplies an
    explicit algorithm policy. Composed FAPI profiles that narrow the
    allowlist must pass `true`.
  * `:max_request_lifetime_seconds` - bound on a signed request's
    `nbf`→`exp` lifetime. Default `3600`
    (FAPI-CIBA §5.2.2's 60 minutes).
  * `:user_code_supported` - whether this OP advertises
    `backchannel_user_code_parameter_supported`. A `user_code` sent when the
    OP or the client's registration does not support it is rejected.
    Default `false`.
  * `:binding_message_max_length` - maximum `binding_message` length in
    graphemes. Default `128`.
  * `:require_binding_message` - when `true`, a request without a
    `binding_message` is rejected with `invalid_binding_message` (FAPI-CIBA
    §5.2.2 requires one when no other unique authorization context exists).
    Default `false`.
  * `:min_client_notification_token_length` - entropy floor for ping/push
    `client_notification_token`s, as a length lower bound. Default
    `22` (128 bits base64-encoded).
  * `:now` - unix seconds or `DateTime`, 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).

---

*Consult [api-reference.md](api-reference.md) for complete listing*
