All notable changes to this project are documented here. The format is based on Keep a Changelog and this project adheres to Semantic Versioning.

[Unreleased]

[0.8.0] - 2026-06-14

Requires attesto ~> 0.6.16.

Added

  • AttestoPhoenix.Store.EctoPARStore — a Postgres-backed Pushed Authorization Request store (RFC 9126), closing the last per-node gap to a fully clusterable authorization server. PAR was the only mutable OAuth store without an Ecto implementation: the default AttestoPhoenix.Store.PAR.ETS keeps the request_uri → params mapping in per-node memory, so a reference pushed to one node could not be resolved when /authorize landed on another — and FAPI 2.0 requires PAR. The new store persists each pushed request so any node resolves a request_uri issued by any other, matching the code/refresh/nonce/replay Ecto stores. fetch/1 is non-consuming (the authorization endpoint may re-enter after a login/consent detour); take/1 is an atomic single-use DELETE … RETURNING.

  • Atomic single-use of the PAR request_uri at completion. The authorization endpoint now claims the pushed reference with the store's atomic take/1 before issuing the code (it was previously consumed after issuance, with the result ignored), so two concurrent completions — on one node or across a cluster — can no longer each mint a code from one pushed request: exactly one wins the claim; the loser is redirected invalid_request_uri and issues nothing. Resolution still uses the non-consuming fetch/1, so a host may establish login/consent and re-enter /authorize with the same reference.

Changed

  • README documents the clustering story end-to-end and the PAR caveat; the :par_store config doc points at EctoPARStore for clustered/FAPI deployments. The default par_store is unchanged (single-node ETS), so existing single-node hosts are unaffected.

[0.7.7] - 2026-06-13

Requires attesto ~> 0.6.16.

Fixed

  • Token endpoint finalizes the authorization code only after the full response is built. The authorization_code grant now calls Attesto.AuthorizationCode.finalize/3 (new in attesto 0.6.16) once the access token, optional refresh token, and id_token have all been minted and recorded successfully. Previously the reuse marker was set the moment the code validated, so any later failure in the same request (a refresh-store write error, an id_token mint fault, a host build_principal callback returning the subject under the wrong key) left the code spent AND flagged as a successful redemption — turning a legitimate client retry into a false reuse attack that revoked the whole refresh-token family. A redemption that validates but fails downstream is now a clean invalid_grant on replay.

[0.7.6] - 2026-06-12

Requires attesto ~> 0.6.13.

Fixed

  • The token endpoint no longer short-circuits a missing PKCE code_verifier as invalid_request. PKCE enforcement is challenge-based: Token.fetch_code_verifier/3 passes the verifier through to Attesto.AuthorizationCode.redeem/4, which requires a matching verifier for a challenge-bound code and collapses a missing OR mismatched verifier to a single invalid_grant (RFC 7636 §4.6). The authorization/PAR endpoint still requires a code_challenge for clients that must use PKCE (RequestPolicy.require_pkce?/2), so a challenge-bound code is always issued. Matches the FAPI ensure-pkce-code-verifier-required test (it expects invalid_grant).

[0.7.5] - 2026-06-10

Requires attesto ~> 0.6.13.

Security

  • PAR request_uri is now single-use (RFC 9126 §2.2 / FAPI 2.0). The reference is consumed once an authorization code is issued (not on the non-consuming fetch that lets the host establish login/consent and re-enter), so a completed flow cannot be replayed within the remaining TTL. An already-consumed reference is rejected as invalid_request_uri. (Flips the conformance PARAttemptReuseRequestUri warning to a clean pass.)
  • UserInfo derives the DPoP htu via RequestContext.canonical_url, like every other endpoint — honouring a configured :htu but otherwise gating X-Forwarded-*/Host on the trusted-proxy allowlist. Previously it fell back to the raw request Host when :htu was unset (its default), the one endpoint that bypassed the host-header trust boundary.

Fixed

  • Sender-constrained (DPoP/mTLS) clients now require PKCE. A FAPI 2.0 client is sender-constrained, and FAPI 2.0 Security Profile §5.3.1.2 / RFC 9700 §2.1.1 mandate PKCE for it even though it authenticates confidentially (e.g. private_key_jwt). RequestPolicy.require_pkce?/2 now forces PKCE whenever client_requires_dpop?/client_requires_mtls? is true, regardless of the global :require_pkce flag, and the token endpoint enforces the matching code_verifier through that same predicate (one source of truth, so the authorization and token endpoints cannot drift). A plain confidential Basic-profile client still follows the global flag. (Flips the conformance EnsurePKCERequired test to a pass.)

[0.7.4] - 2026-06-04

Requires attesto ~> 0.6.13.

Security / FAPI 2.0 conformance

Closes four conformance gaps found by auditing the OpenID FAPI 2.0 test suite source against the implementation:

  • PAR request_uri is bound to the client. The authorization endpoint now rejects a front-channel client_id that does not match the client the request_uri was issued to (RFC 9126 §2.2 / PAREnsureRequestUriIsBoundToClient) instead of silently using the stored client.
  • Unknown/expired PAR request_uriinvalid_request_uri. A urn:ietf:params:oauth:request_uri: reference not in the store now returns the correct invalid_request_uri error rather than falling through to request_uri_not_supported/invalid_request (RFC 9126 §2.2 / PARAttemptToUseExpiredRequestUri). External (non-PAR) references still report request_uri_not_supported.
  • PAR rejects a request_uri parameter. The PAR endpoint rejects a request carrying request_uri (RFC 9126 §2.1 step 2), checked on the raw parameters so it cannot be masked by a request object replacing the set.
  • Client-assertion audience is issuer-only. private_key_jwt assertions at the token, PAR, and introspection endpoints must be audienced to the issuer identifier (FAPI 2.0 §5.3.2.1); the concrete endpoint URL is no longer accepted as aud, closing a confused-deputy gap (PAREndpointAsAudienceFails).

Changed

  • :authorization_response_iss now defaults to true (RFC 9207 authorization-server mix-up defense, mandated by FAPI 2.0). Set false to opt out. Discovery advertises authorization_response_iss_parameter_supported accordingly.
  • Internal: mix dialyzer is clean again. token.ex resolves :principal_kinds by reading the struct field directly (its type admits a list, unlike the callback() | nil reader), and two fail-closed grant-pipeline clauses are documented in .dialyzer_ignore.exs. No behaviour change.

[0.7.3] - 2026-06-04

The FAPI 2.0 Message Signing endpoints on the Phoenix layer: signed authorization responses (JARM), the RFC 7662 / RFC 9701 introspection endpoint, and PAR/JAR hardening. Requires attesto ~> 0.6.13.

Added

  • POST /oauth/introspect — OAuth 2.0 Token Introspection (RFC 7662) with the RFC 9701 signed-JWT response (FAPI 2.0 Message Signing §5.5). Authenticates the caller through the shared AttestoPhoenix.ClientAuthentication core (client_secret_basic/client_secret_post/private_key_jwt), introspects via the conn-free Attesto.Introspection, and negotiates by Accept between the plain JSON response and application/token-introspection+jwt.
  • :introspection_authorize Config callback (caller_client_id, response -> boolean) — authorizes the authenticated introspection caller against the token (RFC 7662 §4 / RFC 9701 §5). Consulted only for an active response; a non-true return (or a raise) downgrades the response to %{"active" => false} so a caller not entitled to the token learns nothing about it. Optional — when unset, every authenticated caller may introspect any token (the single-trust-domain default).
  • The authorization endpoint emits JARM (§5.4) responses for the JARM response_modes (jwt/query.jwt/fragment.jwt/form_post.jwt), and the discovery documents advertise the supported response_modes_supported, authorization_signing_alg_values_supported, the introspection endpoint, and its signing-algorithm metadata.

Changed

  • The PAR endpoint now validates the pushed request as an authorization request at push time (RFC 9126 §2.1 step 3): the request redirect_uri must exactly match one of the client's registered URIs (RFC 6749 §3.1.2.3), and the response_type/PKCE/response_mode must be valid, so an invalid request is refused early rather than only when the request_uri is later resolved at /authorize. The redirect-URI/PKCE/nonce policy is resolved by the new conn-free AttestoPhoenix.AuthorizationServer.RequestPolicy, shared with the authorization endpoint so both validate identically. A host that mounts the PAR endpoint must configure :client_redirect_uris (the authorization endpoint already required it).
  • AttestoPhoenix.ClientAuthentication.Result.client_id falls back to the presented credential identifier so the signed-introspection audience (and the PAR/token client identity) resolves without a separate :client_id callback.
  • OpenID Provider Metadata derives request_parameter_supported (and only then advertises request_object_signing_alg_values_supported) from actual request-object capability — whether the host can resolve a client's trusted JWKS (a :client_jwks callback or an installed :client_store). An install without that capability now advertises request_parameter_supported: false instead of a JAR support it cannot honour.
  • The OAuth 2.0 Authorization Server Metadata document (RFC 8414) now advertises the signed-request-object metadata (require_signed_request_object and request_object_signing_alg_values_supported, RFC 9101 §10.5), matching the OpenID Provider Metadata document so a FAPI client reading either sees identical JAR support. Both documents derive it from the new conn-free AttestoPhoenix.AuthorizationServer.RequestObjectMetadata (no more split, drift-prone assembly).
  • AttestoPhoenix.Config now rejects at boot a :request_object_policy that requires a signed request object (e.g. Policy.fapi_message_signing/0) when no :client_jwks capability is configured. Such a config is unsatisfiable (every authorization request would be rejected) and would otherwise advertise the incoherent pair request_parameter_supported: false + require_signed_request_object: true. Pair the policy with :client_jwks (or an installed :client_store).

[0.7.2] - 2026-06-03

Added

  • :request_object_policy Config key (an Attesto.RequestObject.Policy, default %Policy{} = generic OpenID Connect §6.1). It is enforced at BOTH the PAR endpoint and /authorize: a signed request object pushed to /par is verified there (rejected with invalid_request_object if it fails the policy), and re-verified at /authorize (RFC 9101). On success the PAR store holds the VERIFIED request-object parameters, never the unsigned body values beside them (RFC 9101 §6.3). A non-%Attesto.RequestObject.Policy{} value is rejected at boot. Set Attesto.RequestObject.Policy.fapi_message_signing() for the FAPI 2.0 Message Signing §5.3.1 profile (nbf/exp required and bounded to 60 minutes, typ = "oauth-authz-req+jwt"). Behaviour is unchanged unless a host opts in. Requires attesto ~> 0.6.12.

[0.7.1] - 2026-06-03

Added

  • :client_auth_signing_algs Config key — the JOSE algorithms accepted for private_key_jwt client-assertion signatures, threaded into Attesto.ClientAssertion.verify/5 (via its :accepted_algs opt) and also rendered as token_endpoint_auth_signing_alg_values_supported in discovery. Defaults to Attesto.SigningAlg.fapi_algs/0 (PS256, ES256, EdDSA), so behaviour is unchanged unless a host overrides it. Verification and the advertised metadata now read this one value and cannot drift. Requires attesto ~> 0.6.11.

[0.7.0] - 2026-06-03

A structural refactor of the token/PAR controllers into a reusable authorization-server core, plus a behaviour-module install surface and several correctness fixes. Pre-1.0 minor bump because it carries breaking changes to the host-callback contract (see BREAKING below).

Added

  • Behaviour-module install for host callbacks. The Config keys :client_store, :principal_store, :consent_policy, :scope_policy, :event_sink, :registration, and :claims_provider each resolve their callbacks from a single installed module. Precedence is fixed: an explicit flat callback key wins; else the installed behaviour module if it exports the callback; else nil. The required capabilities (load_client, verify_client_secret, load_principal) are validated by resolution at boot, so a behaviour-module-only install works. Boot-time conformance validation fails fast on a typo'd or partial module.
  • AttestoPhoenix.ClaimsProvider behaviour — the host UserInfo/ID-Token claim source (build_userinfo_claims/3, build_id_token_claims/4).
  • AttestoPhoenix.Callback — one callback dispatcher (function / {m,f} / {m,f,extra}), replacing ~10 duplicated private invoke/2 helpers.
  • AttestoPhoenix.ClientAuthentication and AttestoPhoenix.AuthorizationServer.{SenderConstraint, Token, PAR} — conn-free core modules. The token and PAR controllers are now thin adapters that lift conn facts into data, call the core, and render; the core returns data and audit events rather than writing the conn or emitting events.

Changed

  • BREAKING: the ID-Token extra-claims source is now the separate :build_id_token_claims callback ((client, subject, granted_scopes, requested_claims -> map), and it MUST NOT carry sub). Previously the 4-arity form of :build_userinfo_claims doubled as the ID-Token source; :build_userinfo_claims is now the 3-arity UserInfo source only. Hosts that wired a 4-arity :build_userinfo_claims must move it to :build_id_token_claims.
  • BREAKING: AttestoPhoenix.ClaimsProvider no longer declares build_principal/3; principal building stays solely on AttestoPhoenix.PrincipalStore. Claim sourcing and principal loading are separate concerns.
  • Client-assertion aud now accepts the issuer or the concrete token/PAR endpoint URL (RFC 7523 / OIDC Core §9), widened from issuer-only. The endpoint URL is derived from trusted Config (issuer + path), never the request Host. Still FAPI 2 valid (the issuer remains accepted).
  • Client authentication (RFC 6749 §2.3.1): a request-body client_id presented alongside HTTP Basic is accepted as identification when it matches the Basic userid, and rejected as invalid_request when it conflicts. Only a second credential (body client_secret or client_assertion) is treated as a competing authentication method. The token and PAR endpoints now share one client-authentication implementation, so they no longer diverge.
  • PAR stores the resolved authenticated client_id; when no :client_id callback is configured it leaves the request's presented client_id intact rather than clobbering it. The opaque-struct client[:id]/client["id"] fallback is removed.

[0.6.23] - 2026-06-02

Changed

  • Require the client-authentication assertion aud to be the issuer identifier at both the token and PAR endpoints (FAPI 2). The endpoint URL is no longer accepted as an audience. Requires attesto ~> 0.6.10.

[0.6.22] - 2026-06-02

Changed

  • Advertise only the FAPI 2 client-authentication signing algorithms (PS256, ES256, EdDSA) in token_endpoint_auth_signing_alg_values_supported, matching the underlying enforcement in attesto 0.6.9 which rejects RS256 client assertions. Requires attesto ~> 0.6.9.

[0.6.21] - 2026-06-02

Fixed

  • Return the standard OAuth token endpoint error invalid_request when a client that requires DPoP omits the proof entirely. Presented-but-invalid proofs still return invalid_dpop_proof; the omitted-proof case now matches FAPI's expected token endpoint error classification.

[0.6.20] - 2026-06-02

Added

  • Add :refresh_token_rotation_grace_seconds to AttestoPhoenix.Config and pass it through to Attesto.RefreshToken.rotate/3. The default is now a FAPI retry-compatible 60-second idempotency window for retrying a just-rotated refresh token when the client did not receive or persist the first rotation response; set 0 for strict immediate reuse revocation.

[0.6.19] - 2026-06-02

Fixed

  • Bind refresh tokens to the DPoP proof key only for public clients, as required by RFC 9449. Confidential clients keep refresh tokens bound to the authenticated client, allowing a later refresh request to use a fresh DPoP proof key while still minting the returned access token as DPoP-bound to that current proof.

[0.6.18] - 2026-06-02

Added

  • Add :client_requires_dpop? as a host callback so deployments can mark a client as requiring DPoP-bound token issuance. When such a client calls the token endpoint without a DPoP proof, the controller now rejects the request with invalid_dpop_proof rather than silently issuing an unbound Bearer token.

[0.6.17] - 2026-06-02

Fixed

  • Treat a resolved PAR request_uri as the complete authorization request, so front-channel parameters outside the pushed request object do not augment the request. In particular, a state query parameter that was not included in the pushed request is no longer echoed in the authorization response.

[0.6.16] - 2026-06-02

Fixed

  • Allow PAR requests to carry an explicit dpop_jkt without also requiring a DPoP proof on the PAR request itself. If a PAR DPoP proof is present, an explicit dpop_jkt must still match that proof; otherwise the stored thumbprint is later enforced when the authorization code is redeemed.

[0.6.15] - 2026-06-02

Fixed

  • Carry the DPoP JWK thumbprint from a pushed authorization request into the issued authorization code. A token request that redeems the code with a different DPoP proof key is now rejected instead of minting a token bound to the later key.

[0.6.14] - 2026-06-01

Fixed

  • Verify DPoP proofs at the PAR endpoint and bind stored pushed authorization requests to the verified proof key. If a PAR request includes an explicit dpop_jkt, it must match the verified proof JWK thumbprint; mismatches now return invalid_dpop_proof instead of issuing a request_uri.

[0.6.13] - 2026-06-01

Fixed

  • Accept private_key_jwt client assertions whose aud is the issuer at the token endpoint and PAR endpoint, while continuing to accept endpoint-specific audiences and reject unrelated audiences. This matches FAPI conformance suite client-authentication behavior without relaxing signature, iss/sub, jti, or replay checks.

[0.6.12] - 2026-06-01

Security

  • Reject replayed private_key_jwt client assertions at the token endpoint and PAR endpoint by recording assertion jti values through the configured replay check.
  • Enforce per-client registered grant types when a host provides :client_grant_types, preventing a client registered for one grant from minting tokens through another.
  • Bind PAR request_uri authorization requests to the authenticated pushed request client and store that authenticated client id, rather than trusting a front-channel or body-supplied client_id.

Fixed

  • Preserve keystore-provided per-key alg metadata in the JWKS endpoint. This keeps FAPI deployments that sign ID tokens with PS256 from advertising the same key as RS256.
  • Add the zero-arity issue/0 entrypoint to the Ecto DPoP nonce store so server-issued DPoP nonces work when the store is configured directly as a behaviour module.
  • Decode form-encoded client id and secret values in revocation endpoint Basic authentication, matching the token endpoint.
  • Make the default ETS PAR store tolerate concurrent first-use table creation.

[0.6.11] - 2026-06-01

Fixed

  • Resolve PAR request_uri references non-destructively at the authorization endpoint, so host login or consent re-entry can complete without consuming the pushed request before authorization-code issuance.

Changed

  • Add a fetch callback to AttestoPhoenix.PARStore for authorization-endpoint resolution. Existing custom stores that only implement take/1 still work through a compatibility fallback, but new stores should implement fetch/1.

[0.6.10] - 2026-06-01

Fixed

  • Treat an explicit nil :par_store config value as unset when applying the default ETS PAR store. This prevents PAR from calling nil.put/3 when hosts enable pushed authorization requests without overriding the development PAR store.
  • Apply the same nil-aware defaulting to authorization-endpoint PAR resolution.

[0.6.9] - 2026-06-01

Added

  • Advertise FAPI-required discovery metadata when configured: authorization_response_iss_parameter_supported: true when RFC 9207 authorization-response iss is enabled, and token_endpoint_auth_signing_alg_values_supported from Attesto's asymmetric signing algorithm set for private_key_jwt clients.

[0.6.8] - 2026-06-01

Added

  • Add host-configurable FAPI-oriented authorization-server controls: :require_pushed_authorization_requests rejects direct front-channel authorization requests unless they arrive through a PAR request_uri, and :authorization_response_iss includes the RFC 9207 iss parameter on successful and error authorization responses.
  • Allow hosts to configure the advertised and accepted token endpoint client authentication methods. The token endpoint and PAR endpoint now enforce :token_endpoint_auth_methods_supported when set, so deployments can expose stricter profiles such as private_key_jwt only.
  • Advertise configured token endpoint authentication methods and PAR-required policy in OAuth/OIDC metadata.

[0.6.7] - 2026-06-01

Added

  • Mount POST /oauth/authorize alongside GET /oauth/authorize, matching OpenID Connect Core's requirement that the Authorization Endpoint support both methods.
  • Extend the Ecto authorization-code store with successful-consumption markers and issued-access-token tracking. When a successfully redeemed authorization code is replayed, the token endpoint still returns invalid_grant and now revokes the access token minted by the original code redemption when the Ecto store is configured.

[0.6.6] - 2026-06-01

Fixed

  • Dynamic client registration now preserves inline jwks metadata (RFC 7591 §2) and hands it to the host :register_client callback. Hosts can then return those keys through :client_jwks for request-object and private_key_jwt verification.

[0.6.5] - 2026-06-01

Fixed

  • Return a clean request_uri_not_supported authorization response for unsupported OIDC request_uri references when no PAR store is configured, instead of calling a nil PAR store.

[0.6.4] - 2026-05-31

Changed

  • Replace the direct jason dependency with Elixir's built-in JSON module.

Added

  • Add a test-only req_dpop compatibility check proving that AttestoPhoenix.Plug.Authenticate accepts RFC 9449 DPoP proofs generated by an external Req client plugin. req_dpop is not a runtime dependency.
  • Document req_dpop as an optional Req client companion for tests and internal tooling.

[0.6.3] - 2026-05-31

Added

  • mix attesto_phoenix.install, an upgrade-aware Igniter installer. It is idempotent and re-runnable: it adds the AttestoPhoenix.Config config skeleton (issuer, keystore, repo, the Ecto-backed token stores, a chosen :oauth_path_prefix, and neutral defaults) to the host config, mounts attesto_routes/1 at the chosen prefix into the host router, scaffolds host callback modules implementing the recommended behaviours (ClientStore, PrincipalStore, ScopePolicy, ConsentPolicy, RegistrationStore, EventSink) with documented stub callbacks, and points the host at mix attesto_phoenix.gen.migration for the Ecto tables. igniter is declared as an optional dependency, so the runtime package never forces it on consumers; the task is available to a host that opts into running it. Options: --oauth-path-prefix and --callbacks-module.

  • Configurable OAuth endpoint paths. AttestoPhoenix.Config now accepts an :oauth_path_prefix (default "/oauth", reproducing the historic surface) plus explicit per-endpoint overrides (:authorize_path, :token_path, :par_path, :revocation_path, :registration_path, :userinfo_path) that win when set. Resolver helpers (token_endpoint_url/1, par_endpoint_url/1, revocation_endpoint_url/1, registration_endpoint_url/1, userinfo_endpoint_url/1, authorize_endpoint_url/1, jwks_uri/1, registration_client_uri/2, and the *_path/1 helpers) build absolute URLs from the issuer and the resolved path. The discovery (RFC 8414), OpenID-configuration (OpenID Connect Discovery), and registration (RFC 7591 / RFC 7592) controllers read every advertised URL from these resolvers instead of hardcoding /oauth/*, and to_attesto_config/2 passes the resolved token path to the core builder automatically so the DPoP htu follows the mount. A host that mounts under /mcp/oauth now advertises correct URLs.

  • Named host-contract behaviours documenting the full callback contract with the governing RFC for each callback, as the recommended production shape: AttestoPhoenix.ClientStore, AttestoPhoenix.PrincipalStore, AttestoPhoenix.ScopePolicy, AttestoPhoenix.ConsentPolicy, AttestoPhoenix.RegistrationStore, and AttestoPhoenix.EventSink. Wiring is unchanged: pass an anonymous function, a {module, function} pair, or a {module, function, extra_args} triple per AttestoPhoenix.Config key.

  • Dynamic registration metadata passthrough (RFC 7591 §2). The registration endpoint now validates and carries the known client-identity members (client_name, client_uri, logo_uri, contacts, policy_uri, tos_uri, and related software/JWKS members) through to :register_client so consent screens keep the client's identity. Unknown members are dropped and never promoted to trusted policy; known members are merged under the validated protocol-critical members so they cannot override them.

  • Actionable AttestoPhoenix.Config.new/1 validation errors that name the callback/store/path to add for each enabled feature, and absolute-path validation for :oauth_path_prefix and the per-endpoint overrides.

  • Operations guides wired into the published docs: replay_nonce_production.md, proxy_canonical_host.md, error_envelope.md, consumer_migration.md, and examples.md.

[0.6.2]

  • Advertise response_modes_supported: ["query"] from the RFC 8414 OAuth Authorization Server Metadata endpoint, matching the authorization-code redirect response mode already used by the Phoenix authorization endpoint.

[0.6.1]

  • Emit :token_denied audit/telemetry events for token endpoint failures, including OAuth error, status, client/grant/scope context when available, and sender-constraint presence.
  • Normalize Phoenix callback specs before handing :cert_der to core Attesto protected-resource verification, so function captures, {Module, function}, and {Module, function, extra_args} all work consistently.

[0.6.0]

Initial release: a Phoenix/Ecto OAuth 2.0 / OIDC authorization server layer over attesto.

Added

  • AttestoPhoenix.Config: centralized, validated configuration with neutral host callbacks (:load_client, :verify_client_secret, :load_principal, :authorize_scope, :on_event, and others), deriving the Attesto.Config the protocol layer consumes.
  • AttestoPhoenix.Router: the attesto_routes/1 macro mounting the token, revocation, discovery, JWKS, and optional dynamic-registration endpoints.
  • Controllers for the token endpoint (authorization_code, refresh_token, and client_credentials grants), revocation (RFC 7009), discovery (RFC 8414), JWKS (RFC 7517), and optional dynamic client registration (RFC 7591).
  • AttestoPhoenix.Plug.Authenticate and AttestoPhoenix.Plug.RequireScopes protected-resource plugs with DPoP and mTLS sender-constraint enforcement.
  • Ecto-backed implementations of the attesto store behaviours: code store, refresh store (rotation with reuse detection), DPoP nonce store, and DPoP jti replay check, plus an optional TTL sweeper.
  • mix attesto_phoenix.gen.migration to generate the operational tables.
  • Pushed Authorization Requests (PAR, RFC 9126), private_key_jwt client authentication, signed request object validation, token exchange, UserInfo, registration management cleanup, and Phoenix resource-server plugs.