AttestoPhoenix.Router (AttestoPhoenix v1.3.0)

Copy Markdown View Source

Router macro that mounts the authorization-server endpoints.

use AttestoPhoenix.Router makes the attesto_routes/1 macro available inside a Phoenix.Router. Calling it inside (or alongside) a scope declares the OAuth 2.0 / OpenID Connect server surface:

  • GET /.well-known/oauth-authorization-server - authorization-server metadata (RFC 8414 §3).
  • GET /.well-known/openid-configuration - OpenID Provider configuration (OpenID Connect Discovery 1.0 §4).
  • GET /.well-known/jwks.json - the JSON Web Key Set of the verification keys (RFC 7517 §5; the discovery document's jwks_uri per RFC 8414 §2).
  • GET /.well-known/oauth-protected-resource - protected-resource metadata (RFC 9728 §3), the discovery target of the §5.1 WWW-Authenticate challenge the resource-server plugs emit. For a resource identifier that carries a path, the §3.1 path-inserted form (/.well-known/oauth-protected-resource/mcp) is mounted via :protected_resource_paths - see the option below; the root document alone does not satisfy clients that derive that form.
  • GET /oauth/authorize - the authorization endpoint (RFC 6749 §3.1; OpenID Connect Core 1.0 §3.1.2).
  • POST /oauth/token - the token endpoint (RFC 6749 §3.2).
  • POST /oauth/par - pushed authorization requests (RFC 9126).
  • POST /oauth/revoke - the token revocation endpoint (RFC 7009 §2).
  • POST /oauth/introspect - the token introspection endpoint (RFC 7662 §2), with the RFC 9701 signed-JWT response negotiated by the Accept header.
  • POST /oauth/register - dynamic client registration (RFC 7591 §3.1), mounted only when registration is enabled (see :registration below).
  • DELETE /oauth/register/:client_id - dynamic client registration management cleanup (RFC 7592 §2), mounted with registration.
  • GET and POST /oauth/userinfo - the UserInfo endpoint (OpenID Connect Core 1.0 §5.3); a bearer-authenticated protected resource (RFC 6750 §2.1).
  • GET and POST /oauth/end_session - the end-session endpoint (OpenID Connect RP-Initiated Logout 1.0 §2), mounted only with logout: true.
  • GET /oauth/check_session - the check_session_iframe (OpenID Connect Session Management 1.0 §3.3), mounted only with session_management: true.

The macro emits nothing but Phoenix.Router route entries pointing at this library's controllers; it holds no policy of its own. Every behavioral decision (which clients exist, which scopes are granted, whether DPoP / mTLS binding is offered, whether registration is open) is owned by the host through AttestoPhoenix.Config, which the controllers read at request time.

Placement and pipelines

The discovery, OpenID configuration, and JWKS documents are unauthenticated public metadata (RFC 8414 §5; OpenID Connect Discovery 1.0 §4; RFC 8615). The authorization endpoint does not authenticate the client (RFC 6749 §3.1): the resource owner authenticates through the host's login/consent callbacks, so it carries no client-authentication pipeline. The token, revocation, and registration endpoints authenticate the client from the request itself (RFC 6749 §2.3, RFC 7009 §2, RFC 7591 §3), and the UserInfo endpoint is bearer-authenticated from the Authorization header (RFC 6750 §2.1) by its controller, rather than from a caller session, so they too take no session-bearing pipeline. Supply a :pipeline only to attach transport-level concerns the host wants in front of every endpoint (for example a parser that accepts application/x-www-form-urlencoded at the token endpoint per RFC 6749 §4.4.2, or an HTTPS-enforcing plug).

scope "/" do
  attesto_routes()
end

# or with a host pipeline and a mount prefix:
scope "/" do
  attesto_routes(pipeline: :oauth_server, prefix: "/auth")
end

Options

  • :prefix - path segment prepended to the /oauth/* endpoints (the well-known documents always live at the host root per RFC 8615, so the prefix does not apply to them). Defaults to "".
  • :pipeline - a pipeline name (atom) or list of pipeline names to pipe_through for the mounted routes. Defaults to [] (no extra pipeline; the surrounding scope's pipe_through, if any, still applies).
  • :registration - when true, mounts POST /oauth/register (RFC 7591) and DELETE /oauth/register/:client_id (RFC 7592). Defaults to false. The endpoints still fail closed at request time unless the host has wired the registration callbacks in AttestoPhoenix.Config; this option only controls whether the routes exist, so a deployment that never offers registration presents no registration surface at all.
  • :device - when true, mounts the RFC 8628 device-authorization endpoint and verification page. Defaults to false.
  • :ciba - when true, mounts POST /oauth/bc-authorize, the OpenID Connect CIBA backchannel authentication endpoint. Defaults to false. The endpoint still fails closed at request time unless the host also enables ciba: [enabled: true] in AttestoPhoenix.Config.
  • :logout - when true, mounts GET/POST /oauth/end_session (OpenID Connect RP-Initiated Logout 1.0). Defaults to false.
  • :session_management - when true, mounts GET /oauth/check_session (OpenID Connect Session Management 1.0 §3.3). Defaults to false. The page answers 404 unless the host also enables session_management: [enabled: true] in AttestoPhoenix.Config.
  • :protected_resource_paths - additionally mounts the RFC 9728 §3.1 path-inserted protected-resource metadata URI for the given resource path. RFC 9728 §3.1 derives the well-known URI by inserting the well-known segment between the origin and the resource path: for the resource identifier https://host.example/mcp the metadata lives at /.well-known/oauth-protected-resource/mcp. The root document alone is NOT RFC 9728-complete for such a resource: clients that derive the path-inserted URI from the resource URL (current MCP clients probe it first, before the WWW-Authenticate resource_metadata fallback) miss a host that serves only the root form. Accepts a single-element list (["/mcp"]; a bare "mcp" is normalized to "/mcp"). The served document must satisfy RFC 9728 §3.3 - its resource member must equal the identifier the URI was derived from - so the controller fails closed at request time when the configured resource identifier's path does not match. More than one entry is a compile-time error: one controller document cannot equal two identifiers; multi-resource hosts should use attesto_mcp's AttestoMCP.Router.attesto_mcp_protected_resource_metadata/2, which serves per-resource documents. Defaults to [] (root only, today's behavior).
  • :protected_resource_root - when false, does not mount the root /.well-known/oauth-protected-resource document. Use this when PRM ownership lives elsewhere: a host that mounts attesto_mcp's attesto_mcp_protected_resource_metadata/2 with its root compatibility document enabled should pass protected_resource_root: false here so exactly one package owns each PRM route. Defaults to true (today's behavior).

The library never inspects :registration to make a policy decision: it is a route-existence toggle. Authorization-server metadata advertised at the discovery endpoint is derived from AttestoPhoenix.Config by the discovery controller, not from these macro options.

How protected-resource discovery actually happens

A client calls the resource URL and gets a 401 whose WWW-Authenticate challenge carries a resource_metadata pointer (RFC 9728 §5.1). Modern clients ALSO - often first - derive the §3.1 path-inserted well-known URI from the resource URL itself (https://host.example/mcp/.well-known/oauth-protected-resource/mcp) and probe it before falling back to the challenge pointer. Both URIs must serve the same document, and its resource member must equal the identifier the URI was derived from (§3.3) or the client is required to reject it. A single-resource AS+RS host covers all of this with attesto_routes(protected_resource_paths: ["/mcp"]); a host with multiple protected resources needs per-resource documents and should mount them with attesto_mcp's AttestoMCP.Router.attesto_mcp_protected_resource_metadata/2 instead (passing protected_resource_root: false here if that macro also owns the root document, so each PRM route has exactly one owner).

Summary

Functions

Mounts the authorization-server endpoints. See the module documentation for the route table and the accepted options.

Functions

attesto_routes(opts \\ [])

(macro)

Mounts the authorization-server endpoints. See the module documentation for the route table and the accepted options.