Mint and verify OpenID Connect ID Tokens (OpenID Connect Core 1.0 §2).
An ID Token is the JWT that asserts the authentication of an End-User to
a Relying Party. It is a different artifact from the RFC 9068 access
token Attesto.Token produces, with different semantics: its aud is
the OAuth client_id rather than the protected-resource audience, it
carries no scope claim, and its JOSE header typ is the generic
JWT (NOT at+jwt, which is reserved for access tokens). The two are
kept in separate modules rather than overloading one mint path.
Like Attesto.Token, the operations are pure: they read only the
Attesto.Config passed in. Signing uses the same keystore/kid path
and the same RS256 pinning, and every JOSE call funnels through
JOSE.JWS / JOSE.JWT.verify_strict so the alg whitelist (no none,
no HS256 confusion) lives in one place and fails closed.
Claims (OpenID Connect Core §2)
Every minted ID Token carries:
iss- the configured issuer.sub- the subject identifier for the End-User.aud- the OAuthclient_idof the Relying Party. This is the client, NOT theAttesto.Configaudiencean access token uses.exp/iat- expiry and issued-at, unix seconds.
Conditionally / optionally present:
nonce- the value from the Authentication Request. REQUIRED to be present and identical when the request carried one (OIDC Core §2, §3.1.3.7 item 11).azp- the authorized party. REQUIRED whenaudcontains a value other than theclient_id(OIDC Core §2); always safe to include.auth_time- time of End-User authentication (OIDC Core §2).acr- Authentication Context Class Reference (OIDC Core §2).amr- Authentication Methods References, a JSON array (OIDC Core §2).at_hash- Access Token hash (OIDC Core §3.1.3.6 / §3.3.2.11).c_hash- Authorization Code hash (OIDC Core §3.3.2.11).
There is deliberately no scope claim: scope is a property of the
authorization grant, not of the identity assertion.
Hash claims
at_hash and c_hash use the same construction (OIDC Core §3.1.3.6,
§3.3.2.11): hash the ASCII octets of the access_token / code with
the hash of the ID Token's signature algorithm (SHA-256 for RS256),
take the left-most half of the digest, and base64url-encode it without
padding.
Summary
Functions
The JOSE header typ ID Tokens carry: "JWT" (never "at+jwt").
Mint a signed OpenID Connect ID Token for subject, addressed to the
Relying Party identified by client_id.
The JWS algorithm used to sign ID Tokens. Pinned; verifiers reject anything else.
Verify and decode an ID Token previously minted under the same config.
Types
@type mint_error() ::
:invalid_subject
| :invalid_client_id
| :invalid_extra_claims
| :reserved_claim_conflict
@type mint_opts() :: [ now: DateTime.t() | non_neg_integer(), lifetime: pos_integer(), nonce: String.t(), azp: String.t(), auth_time: non_neg_integer(), acr: String.t(), amr: [String.t()], access_token: String.t(), code: String.t(), extra_claims: %{optional(String.t()) => term()} ]
@type verify_error() ::
:invalid_token
| :invalid_signature
| :unsupported_critical_header
| :unexpected_typ
| :invalid_issuer
| :invalid_audience
| :invalid_azp
| :expired
| :not_yet_valid
| :invalid_claims
| :missing_client_id
| :nonce_required
| :nonce_mismatch
@type verify_opts() :: [ now: DateTime.t() | non_neg_integer(), client_id: String.t(), nonce: String.t() ]
Functions
@spec header_typ() :: String.t()
The JOSE header typ ID Tokens carry: "JWT" (never "at+jwt").
@spec mint(Attesto.Config.t(), String.t(), String.t(), mint_opts()) :: {:ok, String.t()} | {:error, mint_error()}
Mint a signed OpenID Connect ID Token for subject, addressed to the
Relying Party identified by client_id.
client_id becomes the aud claim (OIDC Core §2), distinguishing the
ID Token from a resource-addressed access token; config.audience is
not used here.
Options:
:nonce- the Authentication Request nonce. When supplied it is placed in thenonceclaim, andverify/3then requires a match (OIDC Core §2). Omit only when the request carried no nonce.:azp- the authorized party (OIDC Core §2). REQUIRED by the spec whenaudhas more than one audience.:auth_time- unix time of End-User authentication (OIDC Core §2).:acr- Authentication Context Class Reference (OIDC Core §2).:amr- Authentication Methods References, a list (OIDC Core §2).:access_token- when given, theat_hashclaim is computed from it (OIDC Core §3.1.3.6).:code- when given, thec_hashclaim is computed from it (OIDC Core §3.3.2.11).:extra_claims- a string-keyed map of additional claims (e.g. profile claims). MUST NOT collide with a reserved protocol claim (:reserved_claim_conflict) and MUST have string keys.:now-DateTimeor unix-seconds clock override. Defaults to now.:lifetime- positive seconds; may only shorten the default (a larger value is capped to the default), so a miswired caller cannot mint a long-lived identity assertion.
Returns {:ok, id_token} (compact JWS) or {:error, reason}.
@spec signing_alg() :: String.t()
The JWS algorithm used to sign ID Tokens. Pinned; verifiers reject anything else.
@spec verify(Attesto.Config.t(), String.t(), verify_opts()) :: {:ok, claims()} | {:error, verify_error()}
Verify and decode an ID Token previously minted under the same config.
Mirrors Attesto.Token.verify/3 where the OIDC semantics line up. Runs,
in order:
- Signature. The compact JWS is canonical - three base64url-no-pad
segments - and its RS256 signature verifies against a keystore key
selected by the JWS header
kid. Akidnaming a key we do not hold, or analgother than RS256, fails as:invalid_signature(alg-confusion is impossible). A protected header carrying acritparameter (RFC 7515 §4.1.11) is rejected with:unsupported_critical_header. The JOSE headertyp, when present, MUST be"JWT"; an access-token header such as"at+jwt"is:unexpected_typ. issequals the configured issuer (OIDC Core §3.1.3.7 item 1).audcontains the expectedclient_id(OIDC Core §3.1.3.7 item 3).azp- when present, equals theclient_id(OIDC Core §3.1.3.7 item 4/5).- Required claims are present and well-typed:
suba non-empty string,iata non-negative integer. - Temporal.
expis strictly greater thannow(no skew leeway); aniatmeaningfully in the future is:not_yet_valid. nonce- when a:nonceis supplied, the claim is present and identical (OIDC Core §3.1.3.7 item 11).
Options:
:client_id- the Relying Party client id to require inaud(REQUIRED; OIDC Core §3.1.3.7 item 3).:nonce- the nonce sent in the Authentication Request. When supplied, thenonceclaim MUST be present and equal.:now- clock override.
Returns {:ok, claims} (string-keyed payload) or {:error, reason}.