Attesto.Siop (Attesto v1.14.0)

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 either embeds a public key in sub_jwk and uses its RFC 7638 SHA-256 thumbprint as sub, or uses a self-contained did:jwk / did:key subject whose resolved verification method signs the token. verify/2 ties the subject, verification key, and signature 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 JWK-thumbprint subjects. DID subjects require iss == sub and a protected kid naming the method-defined verification method. Only connection-free did:jwk and did:key are resolved here; network-backed DID methods remain host-owned and fail closed.

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 or DID-resolved holder key;
  • sub must exactly equal the key's RFC 7638 thumbprint, or be the did:jwk / did:key from which the key and protected kid are derived;
  • iss must equal sub for DID subjects, or either sub or https://self-issued.me/v2 for JWK-thumbprint subjects;
  • 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.

JWK-thumbprint subjects may carry sub_jwk in the payload or protected header. A did:jwk subject must instead carry protected kid equal to did:jwk:...#0; a did:key subject must carry protected kid equal to the DID followed by # and its multibase value. A DID response containing sub_jwk, omitting kid, or naming any other verification method is rejected.

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.