Lockspire.Protocol.Jar (lockspire v1.0.0)

Copy Markdown

JWT Secured Authorization Request (JAR) foundation.

Provides unverified decoding, signature verification, and security claims validation of RFC 9101 request objects.

Summary

Functions

Decodes a JWT string without signature verification.

Decrypts a nested JWE request object to return the inner JWS string. If the input is not a JWE (e.g. it is a 3-part JWS), it returns {:ok, jwt} immediately.

Validates RFC 9101 security claims on a decoded JAR request object.

Verifies the signature of a JAR request object using the client's registered public keys.

Types

t()

@type t() :: %Lockspire.Protocol.Jar{claims: map(), header: map()}

validate_claims_reason()

@type validate_claims_reason() ::
  :invalid_claims_options
  | :missing_issuer
  | :invalid_issuer
  | :missing_audience
  | :invalid_audience
  | :missing_expiration
  | :invalid_expiration
  | :expired_token
  | :expiration_too_far
  | :invalid_not_before
  | :invalid_issued_at

Functions

decode(jwt)

@spec decode(String.t()) :: {:ok, t()} | {:error, :invalid_jwt}

Decodes a JWT string without signature verification.

decrypt(jwt, decryption_keys)

@spec decrypt(String.t(), [Lockspire.Domain.SigningKey.t()]) ::
  {:ok, String.t()} | {:error, :decryption_failed}

Decrypts a nested JWE request object to return the inner JWS string. If the input is not a JWE (e.g. it is a 3-part JWS), it returns {:ok, jwt} immediately.

validate_claims(jar, opts)

@spec validate_claims(
  t(),
  keyword()
) :: :ok | {:error, validate_claims_reason()}

Validates RFC 9101 security claims on a decoded JAR request object.

Required options:

  • :expected_client_id (binary) — the iss claim MUST equal this value.
  • :expected_audience (binary) — the aud claim MUST contain this value.

Optional options:

  • :now — a DateTime.t() representing the current time for exp/nbf/iat checks. Defaults to DateTime.utc_now/0.
  • :leeway (non-negative integer, seconds) — clock skew tolerance applied to time-based checks. Defaults to 0.
  • :max_age (positive integer, seconds) — caps the time between now and exp. When set, returns {:error, :expiration_too_far} if exp - now > max_age + leeway. When nil/absent, no ceiling is applied (preserves Phase 21 behavior).

Returns :ok on success, or {:error, reason} where reason is one of:

  • :invalid_claims_options — required options missing or malformed.
  • :missing_issuer / :invalid_issueriss claim missing or mismatched.
  • :missing_audience / :invalid_audienceaud claim missing or mismatched.
  • :missing_expiration / :invalid_expiration / :expired_tokenexp claim missing, malformed, or in the past.
  • :expiration_too_farexp is farther in the future than :max_age + leeway permits (RFC 9101 replay-window mitigation, T-22-03).
  • :invalid_not_beforenbf is present but in the future.
  • :invalid_issued_atiat is present but in the future.

Security: Mitigates T-21-05 (Repudiation) by binding requests to a specific client via iss, and T-21-06 (Information Disclosure) by ensuring requests are intended for this AS via aud.

verify_signature(jwt, client, allowed_algorithms)

@spec verify_signature(String.t(), Lockspire.Domain.Client.t(), [String.t()]) ::
  {:ok, t()}
  | {:error,
     :invalid_signature | :no_matching_key | :invalid_client_keys | :invalid_typ}

Verifies the signature of a JAR request object using the client's registered public keys.

Returns {:ok, %Jar{}} if the JWT signature is valid and the signing key matches a key registered for the client.

Returns {:error, reason} where reason is one of:

  • :invalid_signature — the JWT signature does not verify against the client's keys
  • :no_matching_key — no key could be loaded from the client's JWKS
  • :invalid_client_keys — the client's jwks field is missing, not a map, or cannot be parsed as a JWK or JWK Set by JOSE
  • :invalid_typ — the JWT protected header has a typ value other than oauth-authz-req+jwt or jwt (case-insensitive). RFC 9101 §10.8 cross-JWT-confusion mitigation (T-22-01).

Security: alg=none is never accepted. Only algorithms in the explicit allow-list are permitted, mitigating T-21-03 (Spoofing) and T-21-04 (Tampering).