OAuth 2.0 Dynamic Client Registration endpoint (RFC 7591 §3).
Handles POST /oauth/register. This module owns the HTTP and protocol-framing
concerns only: it parses the RFC 7591 §2 client-metadata document, validates
the requested metadata against the server's advertised policy, mints the
client's credentials through the Attesto core, hands the validated,
issuance-ready attributes to the host's persistence callback, and renders the
RFC 7591 §3.2.1 client information response or the RFC 7591 §3.2.2 error body.
It carries no business-domain logic; the client registry is owned entirely by
the host through the :register_client callback resolved from
AttestoPhoenix.Config.
Disabled by default
Dynamic registration is an open door: a successful request mints a new client
from an otherwise unauthenticated POST. The library therefore mounts this
endpoint only when the host opts in (AttestoPhoenix.Router's
:registration option) AND supplies a :register_client callback
(AttestoPhoenix.Config raises at boot otherwise). Any admission control the
host wants in front of registration - a registration access token
(RFC 7591 §3), an allowlist, rate limiting - lives in the host pipeline ahead
of this action; the library does not assume one.
Wire contract
POST /oauth/register with application/json: the request body is a JSON
client-metadata document (RFC 7591 §3.1). Any other Content-Type is rejected
as invalid_client_metadata rather than parsed through an unintended path. A
metadata document carries nested arrays (redirect_uris, grant_types) that
have no canonical form-encoded representation, so no form encoding is offered
here.
Recognised metadata members (RFC 7591 §2) include redirect_uris,
grant_types, token_endpoint_auth_method, and a space-delimited scope
string. The request is validated member by member against the server's policy
inputs - the scope catalog (AttestoPhoenix.Config's :scopes_supported),
the supported grant types, and the supported token-endpoint auth methods -
and the first failure is returned.
Issued credentials
This controller owns credential generation: it mints the client_id and (for
a confidential client, i.e. any token_endpoint_auth_method other than
none) a high-entropy client_secret via Attesto.Secret (RFC 6749 §2.3.1
high-entropy secret). The plaintext secret appears in the RFC 7591 §3.2.1
response exactly once, accompanied by the REQUIRED client_secret_expires_at
(0, non-expiring); only its one-way hash is handed to the host for
persistence, so a leaked client store yields no usable secret.
Responses
Success renders HTTP 201 with the RFC 7591 §3.2.1 client information response
(the registered metadata plus the synthesised client_id, the optional
client_secret with its REQUIRED client_secret_expires_at, and
client_id_issued_at). Failure renders the RFC 7591
§3.2.2 error body ({"error": code, "error_description": ...}) with the
RFC 7591 §3.2.2 codes invalid_redirect_uri and invalid_client_metadata.
A host store rejection surfaces as invalid_client_metadata (the request
named a client the store would not accept) rather than a 500. Both success
and error responses carry no-store cache headers (RFC 7234 §5.2) because the
body can carry a freshly minted credential.
Event
A successful registration emits a :client_registered event (RFC 7591)
through AttestoPhoenix.Event carrying the issued client_id. The plaintext
secret is never placed on the event.
Summary
Functions
Dynamic client registration action (RFC 7591 §3.1).
Dynamic client registration management delete action (RFC 7592 §2).
Functions
@spec create(Plug.Conn.t(), map()) :: Plug.Conn.t()
Dynamic client registration action (RFC 7591 §3.1).
Validates the client-metadata document, mints the client's credentials, persists via the host callback, and renders either the RFC 7591 §3.2.1 client information response or an RFC 7591 §3.2.2 error. Every response carries no-store cache headers (RFC 7234 §5.2).
@spec delete(Plug.Conn.t(), map()) :: Plug.Conn.t()
Dynamic client registration management delete action (RFC 7592 §2).
The OpenID conformance suite uses this as cleanup for dynamically registered
clients. A host must wire both :client_registration_access_token_hash and
:unregister_client; absent either callback, the endpoint fails closed.