Signed OpenID Connect Request Object verification (JAR, RFC 9101 / OIDC §6.1).
This module verifies a compact JWT request object against trusted client JWKs supplied by the host. It deliberately rejects unsigned request objects: a host that wants request objects is opting into integrity protection, not a second unsigned parameter encoding.
Summary
Types
@type verify_error() ::
:invalid_request_object
| :request_not_supported
| :invalid_signature
| :invalid_issuer
| :invalid_audience
| :invalid_typ
| :expired
| :not_yet_valid
| :unsupported_critical_header
@type verify_opts() :: [ now: DateTime.t() | non_neg_integer(), issuer: String.t() | nil, audience: String.t() | [String.t()], accepted_algs: [Attesto.SigningAlg.alg()], require_nbf: boolean(), max_nbf_age_seconds: pos_integer() | nil, require_exp: boolean(), require_iat: boolean(), require_jti: boolean(), require_client_id_claim: boolean(), max_lifetime_seconds: pos_integer() | nil, accepted_typ: [String.t() | nil] | nil, string_valued_claims: [String.t()] ]
Functions
@spec verify(String.t(), map() | [map()] | map(), verify_opts()) :: {:ok, map()} | {:error, verify_error()}
Verify and return a string-keyed parameter map from a signed request object.
The object must carry iss, client_id, and aud. iss must match the
object's client_id and the caller-supplied :issuer; aud must match the
caller-supplied :audience.
Opts implementing the RFC 9101 / FAPI Message Signing 2.0 §5.3.1 strict-JAR policy. Every one defaults to the lenient JAR/OIDC §6.1 behaviour, so a caller that passes none observes no change:
:accepted_algs- JOSE algorithms a candidate trusted key may use. Defaults toSigningAlg.fapi_algs/0(PS256, ES256, EdDSA).:require_nbf- whentrue, reject an object without annbfclaim. Defaults tofalse. (RFC 9101 / FAPI Message Signing 2.0 §5.3.1.):max_nbf_age_seconds- when set, reject annbfolder thannow - N. Defaults tonil(no lower bound).:require_exp- whentrue, reject an object without anexpclaim. Defaults tofalse.:require_iat- whentrue, reject an object without a validiatNumericDate claim. Defaults tofalse. (CIBA Core §7.1.1 makesiatREQUIRED in a signed authentication request.):require_jti- whentrue, reject an object without a non-empty stringjticlaim. Defaults tofalse. (CIBA Core §7.1.1 makesjtiREQUIRED; replay tracking against it is the caller's concern.):require_client_id_claim- whenfalse, skip the requirement that the object carry aclient_idclaim equal toiss. RFC 9101 request objects carryclient_id; a CIBA signed authentication request (CIBA Core §7.1.1) does not - itsissalone names the client and is still matched against the caller-supplied:issuer. Defaults totrue(the RFC 9101 behaviour).:max_lifetime_seconds- when set, require validnbfandexpNumericDate anchors and reject anexpgreater thannbf + N. Defaults tonil(no lifetime bound).:accepted_typ- when a list, require the JOSE headertypto be a member;nilin the list permits an absenttyp. Defaults tonil, which accepts anytypincluding its absence.:string_valued_claims- when a list of claim names, each named claim that is present MUST be a JSON string, else:invalid_request_object. This runs BEFORE the claim → parameter coercion, so a non-string value (number, boolean, array, object) is rejected rather than stringified / space-joined / JSON-encoded, and a malformed value can never reachclaims_to_params/1's coercion (so it cannot raise). Defaults to[](no typing constraint - the lenient JAR/OIDC §6.1 coercion). CIBA Core §7.1.1 uses this to require its known authentication-request parameters be JSON strings in a signed request; RFC 9101 JAR leaves it unset.
@spec verify_with_claims(String.t(), map() | [map()] | map(), verify_opts()) :: {:ok, map(), map()} | {:error, verify_error()}
Like verify/3, but also returns the raw verified JWT claims alongside the
parameter map: {:ok, params, claims}.
A profile that must act on reserved claims the parameter map deliberately
drops - CIBA Core §7.1.1 needs the signed request's jti and exp to
implement replay defense at the host boundary - uses this to read them
without decoding/verifying the JWT a second time. params is identical to
what verify/3 returns.