Attesto.Siop (Attesto v1.12.2)

Copy Markdown View Source

SIOPv2 Self-Issued ID Token verification for the Relying Party role.

A Self-Issued ID Token is not verified against an issuer-owned JWKS. The holder embeds a public key in sub_jwk, signs the ID Token with that key, and uses the key's RFC 7638 SHA-256 thumbprint as sub. verify/2 ties all three values together before returning the verified subject and public key.

This module implements the JWK Thumbprint Subject Syntax Type from Self-Issued OpenID Provider v2 draft 13, section 11.1. It accepts sub_jwk in the payload, as the current draft specifies, and in the protected JOSE header for wallet interoperability. If both locations are present, their JWK maps must be identical.

The current draft identifies a Self-Issued ID Token with iss == sub. https://self-issued.me/v2, used by the earlier static-discovery model, is also accepted for compatibility. DID subjects are deliberately out of scope: they require method-specific DID resolution rather than an embedded sub_jwk.

Verification is conn-free and fail-closed:

  • the compact JWS must be canonical and carry no unsupported critical headers;
  • alg must be an Attesto-supported asymmetric algorithm allowed by RP policy and compatible with a public verification JWK;
  • the signature must verify strictly with the embedded holder key;
  • sub must exactly equal the key's RFC 7638 thumbprint;
  • iss must equal either sub or https://self-issued.me/v2;
  • aud must contain the RP Client ID and nonce must exactly match the Authentication Request;
  • exp and iat are required non-negative NumericDates; nbf is optional but, when present, must also be a non-negative NumericDate. Expired and not-yet-valid tokens are rejected.

Claims other than the cryptographically bound sub remain self-attested.

Summary

Functions

Verify a Self-Issued ID Token and return its subject and holder public JWK.

Verify a Self-Issued ID Token against an RP Client ID and request nonce.

Types

verified()

@type verified() :: %{subject: String.t(), jwk: map()}

verify_error()

@type verify_error() ::
  :invalid_token
  | :unsupported_critical_header
  | :unexpected_typ
  | :invalid_alg
  | :missing_sub_jwk
  | :invalid_sub_jwk
  | :invalid_signature
  | :invalid_subject
  | :invalid_issuer
  | :invalid_audience
  | :invalid_nonce
  | :invalid_claims
  | :expired
  | :not_yet_valid

verify_opts()

@type verify_opts() :: [
  audience: String.t(),
  nonce: String.t(),
  now: DateTime.t() | non_neg_integer(),
  accepted_algs: [Attesto.SigningAlg.alg()]
]

Functions

verify(id_token, opts)

@spec verify(String.t(), verify_opts()) ::
  {:ok, verified()} | {:error, verify_error()}

Verify a Self-Issued ID Token and return its subject and holder public JWK.

Required options:

  • :audience - the RP Client ID sent in the Authentication Request. The token's aud may be this string or an all-string array containing it.
  • :nonce - the nonce sent in the Authentication Request. SIOPv2 requires it to be present and identical in the Self-Issued ID Token.

Optional options:

  • :now - clock reference as a DateTime or Unix seconds.
  • :accepted_algs - holder signature algorithms accepted by RP policy. Defaults to Attesto.SigningAlg.allowed/0; none, MAC algorithms, and algorithms unsupported by Attesto remain rejected even if listed.

The convenience verify/3 form accepts id_token, audience, and nonce as positional arguments and applies the default clock and algorithm policy.

verify(id_token, audience, nonce)

@spec verify(String.t(), String.t(), String.t()) ::
  {:ok, verified()} | {:error, verify_error()}

Verify a Self-Issued ID Token against an RP Client ID and request nonce.