Current section
Files
Jump to
Current section
Files
attesto_client
CHANGELOG.md
CHANGELOG.md
# Changelog
All notable changes to this project are documented here. The format is based on
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project
adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
## [2.2.0] - 2026-07-25
### Added
- Support RFC 9864 `Ed25519` and `Ed448` identifiers across issuer-signed JWT
verification and client artifact builders while retaining legacy `EdDSA`.
- Add independent Node crypto parity for Ed25519 and Ed448 signatures and OIDC
detached hash claims.
### Security
- Bind ID Token `at_hash`, `c_hash`, and `s_hash` calculation to the verified
issuer JWK. Legacy `EdDSA` now selects SHA-512 with a 32-byte left half for
Ed25519 and SHAKE256 with 114 bytes of output and a 57-byte left half for
Ed448.
- Move JARM onto the shared hardened verifier: require a unique eligible key,
honor JWK `alg`, `use`, and `key_ops`, reject RSA keys below 2048 bits, bind
every algorithm to its key type and curve, and apply FAPI's Ed25519-only
Edwards policy by default. An explicit `accepted_algs` list remains a
deliberate non-FAPI override unless `enforce_fapi_alg_policy: true` is set.
- Validate every explicit outbound signing algorithm against its private key
before signing, including exact Edwards curve binding.
### Changed
- Require Attesto 1.3 or later for key-aware OIDC hashing and trusted-key
algorithm validation.
- Keep the JOSE security floor at 1.11.12 while widening the upper-compatible
range to all 1.x releases, so a future 1.x release with native OTP SHA-3 and
Ed448 detection is reachable without another AttestoClient release.
## [2.1.1] - 2026-07-17
### Security
- Restrict the optional Plug integration to the security-patched releases on
every supported minor line: Plug 1.16.6, 1.17.4, 1.18.5, 1.19.5, and 1.20.3
and later 1.x releases.
- Raise the JOSE floor to 1.11.12, excluding releases affected by
GHSA-9mg4-v392-8j68 / CVE-2023-50966, the 1.11.7-1.11.8 packages that
included development-only tooling as runtime dependencies, and the
1.11.9-1.11.10 releases with broken EC key conversion on OTP 28. JOSE
1.11.11 fixed that conversion but could encode Elixir `nil` as the JSON
string `"nil"` with OTP's built-in JSON module; 1.11.12 fixes that too.
- Refresh the development lock to Mint 1.9.3, resolving
EEF-CVE-2026-59249 / CVE-2026-59249.
- Extend discovery and JWKS SSRF checks across non-global IPv4, translated
IPv6, deprecated site-local, and local-use NAT64 destinations while retaining
globally reachable anycast and public NAT64 targets.
- Require a replay callback for every DPoP request; the former test-only
acknowledgement flag can no longer disable replay protection.
- Keep internal token, DPoP, certificate, and nonce rejection reasons out of
OAuth error responses.
## [2.1.0] - 2026-07-16
### Added
- Add `AttestoClient.ResourceServer`, a supervised remote-issuer RFC 9068 JWT
access-token verifier. It performs exact issuer and audience checks, strict
algorithm/key selection, required claim and time validation, exact scope
enforcement, optional subject/client and token-age/lifetime policy, explicit
warming/readiness, and fail-closed DPoP/mTLS confirmation binding.
- Add coordinated discovery/JWKS caching with bounded key count, configurable
fresh and stale intervals, transient-error-only stale-key use, single-flight
refresh, unknown-`kid` rotation refresh, response-size limits, and refresh-
storm throttling/backoff.
- Add `AttestoClient.ResourceServer.Plug` for Bearer, DPoP, and mTLS protected
resources, including fail-closed DPoP replay wiring and RFC 6750 error and
insufficient-scope responses. Upstream key availability failures produce a
detail-free `503` rather than an `invalid_token` challenge.
- Add bidirectional PyJWT parity coverage for RFC 9068 signing and verification,
including an independently signed unknown-`kid` key-rotation flow.
### Security
- Remote discovery and JWKS refreshes retain HTTPS, exact-issuer, SSRF, and
redirect protections; disable Req retries and bound each coordinated refresh
with an application-configured deadline.
## [2.0.0] - 2026-07-16
### Security
- Add a complete OpenID Connect Authorization Code flow that always uses S256
PKCE, binds callbacks to high-entropy state, nonce, and a mandatory opaque
application browser-session value, validates the response issuer when
supplied, atomically consumes expiring transaction state, pins the registered
ID Token algorithm, and gives the code exchange a bounded deadline with
retries disabled.
- ID Token verification now rejects ambiguous eligible JWKS keys and RSA keys
below 2048 bits, honors JWK `use` / `key_ops`, validates optional `nbf`, and
uses constant-time nonce/subject comparisons. A missing `kid` remains valid
only when exactly one eligible verification key exists.
- Add bounded single-flight refresh-token rotation. Concurrent callers for one
application key share one request and one result; timeout and worker failure
wake all waiters and clear the flight.
- Require HTTPS redirect URIs except for native-client loopback HTTP redirects.
- Raise the Req dependency floor to 0.6.1, the first release patched for
EEF-CVE-2026-49755 decompression-bomb denial of service.
### Added
- `AttestoClient.AuthorizationTransaction.Store` and a bounded, single-node ETS
implementation with atomic insert/consume and monotonic expiry.
- `AttestoClient.Token` refresh and RFC 7009 revocation operations,
`AttestoClient.RefreshCoordinator`, `AttestoClient.TokenSet`, and verified
refresh ID Token results.
- `AttestoClient.Logout.url/1` for OpenID Connect RP-Initiated Logout.
- Structural discovery validation for required OIDC endpoints and capability
fields before an authorization flow uses them.
- Adversarial coverage for replay and concurrent state consumption, expiry,
issuer/audience/nonce confusion, ambiguous or ineligible keys, weak RSA,
malformed discovery, refresh races, independent refreshes, and deadlines.
- Authorization-request `max_age` is retained in the transaction and enforced
against the callback ID Token's `auth_time`.
### Changed
- Supplying `:access_token` to `AttestoClient.IDToken.verify/2` validates
`at_hash` when present but no longer requires the claim for a token-endpoint
ID Token, where OIDC Core permits omission. Pass `require_at_hash: true` for
front-channel or profile rules that require it.
- Authorization decisions, durable token persistence, refresh-result
compare-and-swap, and session-retention/termination remain application-owned.
- The Elixir floor remains 1.18: this package and its required `attesto`
dependency both depend on Elixir's built-in `JSON` module.
### Migration
- `AttestoClient.AuthorizationCode.start/2` and `callback/3` now require the
same opaque `:browser_binding`. Generate or retain it in the initiating user
agent's secure application session; callbacks with a missing or different
binding consume state and fail before token exchange.
- The key-selection and minimum-RSA checks intentionally reject tokens that 1.x
could accept. Applications with duplicate `kid` values, multiple eligible
kid-less keys, encryption-only verification keys, or RSA keys below 2048 bits
must correct their JWKS before upgrading.
- Because those security checks tighten existing public verification APIs,
this release is a **2.0.0** major release.
## [1.1.0] - 2026-07-07
### Changed
- `AttestoClient.Discovery.fetch/2` now compares the document's `issuer`
**exactly** against the supplied issuer (RFC 8414 §3.3 / OpenID Connect
Discovery 1.0 §4.3) instead of normalising a trailing slash away. A
slash-terminated path issuer (e.g. a multi-tenant issuer, or the OpenID
conformance suite's `https://.../test/a/<alias>/`) was previously rejected
with `:issuer_mismatch`; conversely, two identifiers that differ only by a
trailing slash no longer match. The trailing slash is still removed when
constructing the well-known request URL, as both specs require.
### Added
- `AttestoClient.IDToken.verify/2` accepts `allow_unsigned: true`, an explicit
opt-in for the OIDC Core §3.1.3.7 case: a client that registered
`id_token_signed_response_alg` `none` and received the ID Token directly from
the token endpoint over TLS may accept an unsigned (`alg: "none"`) token. All
claim checks still run; the signature part must be empty; signed tokens are
unaffected; the default remains to reject unsigned tokens.
## [1.0.0] - 2026-07-04
First stable release; the public API is now under semantic versioning. No
functional change from 0.6.0. Requires `attesto ~> 1.0`.
## [0.6.0] - 2026-06-21
### Security
- `AttestoClient.Discovery` hardens its discovery/JWKS fetches against SSRF:
redirects are no longer followed (a 3xx surfaces as `{:http_status, _}` rather
than being chased to its `Location`), and a URL whose host resolves to a
loopback, private, link-local, or unique-local address is rejected with
`:blocked_host` — so an attacker-influenced `issuer`/`jwks_uri` cannot point a
server-side fetch at an internal service or the cloud metadata endpoint. An
unresolvable host is left to the transport.
### Added
- `AttestoClient.IDToken` - verify OpenID Connect ID Tokens against authorization
server JWKS/discovery, including issuer, audience, `azp`, expiration,
issued-at, nonce, `max_age`/`auth_time`, and detached `at_hash` / `c_hash` /
`s_hash` validation. Interop-tested against `Attesto.IDToken.mint/4`.
- `AttestoClient.IdentityAssertion` - build Identity Assertion JWT
Authorization Grant assertions (ID-JAG / EMA) with the `oauth-id-jag+jwt`
header and the required `iss`/`sub`/`aud`/`client_id`/`jti`/`iat`/`exp`
claims. Interop-tested against `Attesto.IdentityAssertion.verify/3`.
- `AttestoClient.PKCE` - generate S256 PKCE verifier/challenge pairs, delegating
challenge computation to `Attesto.PKCE.challenge/1` so generated pairs verify
under `Attesto.PKCE.verify/3`.
- `AttestoClient.SignedIntrospection` - verify RFC 9701 signed token
introspection responses against authorization-server JWKS/discovery.
Interop-tested against `Attesto.SignedIntrospection.response_jwt/4`.
- `AttestoClient.UserInfo` - verify signed OpenID Connect UserInfo JWT
responses, including issuer/audience/subject checks and optional binding to a
previously verified ID Token subject.
- Internal AttestoClient.Verifier shared by the AS-signed JWT verifiers (not
public API; hidden from docs).
- `AttestoClient.ClientAssertion` - build `private_key_jwt` client
authentication assertions (RFC 7523 / OpenID Connect Core §9), signed with the
client's own key. Carries a cross-language parity test against an independent
PyJWT reference verifier, plus in-family interop against
`Attesto.ClientAssertion.verify/5`.
- `AttestoClient.RequestObject` - build signed authorization request objects
(JAR, RFC 9101 / FAPI 2.0 Message Signing §5.3.1): the caller's authorization
parameters wrapped with the iss/aud/iat/nbf/exp/jti envelope and the
`oauth-authz-req+jwt` typ, signed with the client's key. The lifetime is
bounded to the FAPI 60-minute window. Parity-tested against an independent
PyJWT reference and in-family against `Attesto.RequestObject.verify/3` under
the FAPI Message Signing policy.
- Internal AttestoClient.Builder shared by the builders (not public API; hidden from docs).
- `AttestoClient.JARM` - verify a signed authorization response (JARM, FAPI 2.0
Message Signing §5.4): JWS signature against the authorization server's JWKS
(FAPI algorithm allow-list, `none` rejected, kid selection), plus `iss`/`aud`/
`iat`/`exp`, returning the response parameters. Parity-tested by verifying a
JARM token signed by an independent PyJWT signer (the flipped external
direction) and one signed by `Attesto.JARM.response_jwt/4` (in-family).
- `AttestoClient.Discovery` - fetch and read OAuth 2.0 / OpenID Connect
authorization-server metadata and JWKS (RFC 8414 / OpenID Connect Discovery
1.0) over `Req`, with `https` and RFC 8414 §3.3 issuer-match validation.
Verified in-family against `Attesto.OpenIDDiscovery.metadata/2` output.
### Changed
- Require `attesto ~> 0.9` so the client mirror can use the current ID Token,
ID-JAG, PKCE, signed introspection, signing-algorithm, and hash primitives.