Skip to main content

lib/attesto_phoenix.ex

defmodule AttestoPhoenix do
  @moduledoc """
  A Phoenix/Ecto OAuth 2.0 / OIDC authorization-server and
  resource-server layer built on top of `Attesto`.

  `Attesto` is transport-agnostic: it implements the pure, effect-free
  protocol primitives - JWT mint/verify with per-key algorithm metadata
  (RFC 7519), DPoP sender-constraint proofs (RFC 9449), mutual-TLS binding
  (RFC 8705), PKCE (RFC 7636), the JWK Set (RFC 7517), authorization-server
  metadata (RFC 8414), `private_key_jwt` client assertions (RFC 7523),
  signed request objects (RFC 9101), and the scope grant-form algebra. It
  deliberately carries no HTTP, no persistence, and no identity model.

  `attesto_phoenix` adds exactly the two things a running server needs and
  the core leaves out: a transport (HTTP endpoints and protected-resource
  plugs) and persistence (Ecto-backed implementations of the core store
  behaviours). Everything that is inherently application policy stays the
  host's, supplied through a small set of neutral configuration callbacks.

  ## The split

  The library keeps the same boundary `Attesto` draws - *protocol* versus
  *policy* - and adds a third concern, *transport*:

    * **Protocol (core).** `Attesto.Token`, `Attesto.DPoP`,
      `Attesto.MTLS`, `Attesto.PKCE`, `Attesto.Scope`, `Attesto.JWKS`,
      `Attesto.ClientAssertion`, `Attesto.RequestObject`, and
      `Attesto.Discovery`. Pure functions over bytes and claims. This layer
      is reused verbatim; this package adds no crypto and forwards every
      protocol decision to it.

    * **Transport (here).** Controllers behind a router macro that mount
      authorization, token, pushed-authorization-request (RFC 9126),
      revocation (RFC 7009), discovery (RFC 8414), JWK Set (RFC 7517),
      UserInfo, and optional dynamic-registration (RFC 7591) endpoints, plus
      protected-resource plugs that verify a Bearer/DPoP access token and
      enforce its sender-constraint binding. The controllers and plugs use
      the core OAuth-error / `WWW-Authenticate` helpers so every failure is
      an RFC 6749 §5.2 / RFC 6750 §3 response, never a silent reject.

    * **Persistence (here).** Ecto schemas that implement the core store
      behaviours for authorization codes and refresh tokens, and - for
      clustered correctness - DPoP nonces and proof `jti` replay records.
      Migration scaffolding is a `mix` generator that writes the migration
      into the host application; this package owns no migration of its own.

    * **Policy (host application).** The client registry and its
      revocation rule, client-secret hashing, the subject/principal model,
      the scope catalog, signing keys, and the audit log. These are
      injected as the callbacks documented on `AttestoPhoenix.Config`, so
      the library never hardcodes one application's identity model.

  ## Configuration

  All behaviour is centralized in `AttestoPhoenix.Config`. It is the single
  source of truth read by every controller and plug: it validates the
  required keys at build time (raising `ArgumentError` so misconfiguration
  fails closed at boot), applies neutral defaults, and derives the
  `Attesto.Config` the protocol layer runs against via
  `AttestoPhoenix.Config.to_attesto_config/2`.

  Anything that is application policy is a callback rather than a baked-in
  assumption, named in OAuth terms:

    * client lookup -> `:load_client`
    * client-secret verification -> `:verify_client_secret`
    * client public keys -> `:client_jwks`
    * subject/principal resolution -> `:load_principal`
    * scope catalog / narrowing -> `:scopes_supported` and/or
      `:authorize_scope`
    * audit / telemetry -> `:on_event` (optional, no-op by default)
    * dynamic client persistence -> `:register_client` (only when
      registration is enabled)
    * mTLS certificate extraction -> `:cert_der` (only when mTLS is
      enabled)
    * HTTPS / proxy trust -> `:require_https` and `:trusted_proxies`

  See `AttestoPhoenix.Config` for the full key reference and the default
  for each value.

  ## Mounting the routes

  `AttestoPhoenix.Router` provides the `attesto_routes/1` macro, which
  mounts the authorization-server endpoints under a scope the host chooses.
  Discovery and the JWK Set are public; the token and revocation endpoints
  authenticate the client via the `:load_client` / `:verify_client_secret`
  callbacks.

  ## Entry points

    * `AttestoPhoenix.Config` - the validated configuration every
      controller and plug reads, and the derivation of the protocol
      `Attesto.Config`.
    * `AttestoPhoenix.Router` - the `attesto_routes/1` macro that mounts
      the HTTP surface.

  See the `README` for the supply/own breakdown, the router and plug usage,
  and the migration generator.
  """
end