The host-owned OAuth client registry contract (RFC 6749 §2 / §3.1.2).
The library never owns the client registry: it resolves a client from its
identifier, verifies its secret in constant time, and reads the per-client
attributes the authorization, token, PAR, and revocation endpoints need. A
host implements this behaviour and wires each callback into
AttestoPhoenix.Config as an anonymous function, a {module, function}
pair, or a {module, function, extra_args} triple. This module is the
contract those Config keys install; it is the recommended production shape
but the wiring is unchanged from passing the callbacks individually.
Each @callback corresponds to the identically named AttestoPhoenix.Config
key:
load_client/1(:load_client, required)verify_client_secret/2(:verify_client_secret, required)client_id/1(:client_id)client_jwks/1(:client_jwks)client_redirect_uris/1(:client_redirect_uris)client_public?/1(:client_public?)client_native?/1(:client_native?)client_requires_mtls?/1(:client_requires_mtls?)client_requires_dpop?/1(:client_requires_dpop?)client_grant_types/1(:client_grant_types)
The client term is opaque to the library: whatever
load_client/1 returns is threaded back into the other callbacks unchanged.
Summary
Callbacks
Whether this client's logout_token MUST carry a sid claim
(backchannel_logout_session_required, Back-Channel Logout 1.0 §2.2).
Defaults to false when the callback is not exposed.
The client's registered backchannel_logout_uri (OpenID Connect Back-Channel
Logout 1.0 §2.2), or nil when the client is not back-channel-logout capable.
When present, the OP records a logout session at ID-Token mint and POSTs a
logout_token here when the session ends.
The client's registered OpenID Connect CIBA metadata (CIBA Core §4), as a map
with :token_delivery_mode (:poll | :ping | :push),
:client_notification_endpoint (the ping-mode
backchannel_client_notification_endpoint), :request_signing_alg (the
registered backchannel_authentication_request_signing_alg), and
:user_code_parameter (the backchannel_user_code_parameter boolean). A
client not registered for CIBA returns %{} (or nil), which the backchannel
authentication endpoint treats as unauthorized_client.
Whether this client's rendered frontchannel_logout_uri must carry iss and
sid query parameters (frontchannel_logout_session_required, Front-Channel
Logout 1.0 §2). Defaults to false when the callback is not exposed.
The client's registered frontchannel_logout_uri (OpenID Connect
Front-Channel Logout 1.0 §2), or nil when the client is not
front-channel-logout capable. When present, the OP records a logout session
at ID-Token mint and renders the URI in an iframe on the end-session logout
page when the session ends.
The grant types registered for this client (RFC 7591 §2).
The client's OAuth identifier (RFC 6749 §2.2), extracted from the host's client representation.
The client's trusted public JWK Set for private_key_jwt client
authentication (RFC 7523 / OpenID Connect Core §9). Returns nil for a
client that does not authenticate with a signed assertion.
Whether the client is an installed native application (RFC 8252 / BCP 212).
The client's registered post_logout_redirect_uris (OpenID Connect
RP-Initiated Logout 1.0 §2). The end-session endpoint exact-matches the
request post_logout_redirect_uri against this set; a client exposing none
has no validated return URI (fail closed — the OP renders its own page).
Whether the client may authenticate without a secret and rely on PKCE (RFC 6749 §2.1 / RFC 7636).
The client's registered redirect URIs (RFC 6749 §3.1.2.2). The authorization
endpoint exact-matches the request redirect_uri against this set
(RFC 6749 §3.1.2.3); a client exposing none rejects every authorization
request (fail closed).
Whether the client requires DPoP-bound token issuance (RFC 9449).
Whether the client requires mTLS-bound token issuance (RFC 8705).
Resolve an OAuth client by its identifier (RFC 6749 §2.2).
Constant-time verification of a presented client secret (RFC 6749 §2.3.1).
Types
@type client() :: term()
The host's opaque client representation (e.g. an Ecto struct).
Callbacks
Whether this client's logout_token MUST carry a sid claim
(backchannel_logout_session_required, Back-Channel Logout 1.0 §2.2).
Defaults to false when the callback is not exposed.
The client's registered backchannel_logout_uri (OpenID Connect Back-Channel
Logout 1.0 §2.2), or nil when the client is not back-channel-logout capable.
When present, the OP records a logout session at ID-Token mint and POSTs a
logout_token here when the session ends.
The client's registered OpenID Connect CIBA metadata (CIBA Core §4), as a map
with :token_delivery_mode (:poll | :ping | :push),
:client_notification_endpoint (the ping-mode
backchannel_client_notification_endpoint), :request_signing_alg (the
registered backchannel_authentication_request_signing_alg), and
:user_code_parameter (the backchannel_user_code_parameter boolean). A
client not registered for CIBA returns %{} (or nil), which the backchannel
authentication endpoint treats as unauthorized_client.
Whether this client's rendered frontchannel_logout_uri must carry iss and
sid query parameters (frontchannel_logout_session_required, Front-Channel
Logout 1.0 §2). Defaults to false when the callback is not exposed.
The client's registered frontchannel_logout_uri (OpenID Connect
Front-Channel Logout 1.0 §2), or nil when the client is not
front-channel-logout capable. When present, the OP records a logout session
at ID-Token mint and renders the URI in an iframe on the end-session logout
page when the session ends.
The grant types registered for this client (RFC 7591 §2).
When the host exposes this callback, the token endpoint rejects a requested
grant_type not in the returned list before dispatching to the grant
implementation. Return nil only when the host has no per-client grant
registry and wants the package's legacy configured-supported-grants behavior.
The client's OAuth identifier (RFC 6749 §2.2), extracted from the host's client representation.
The client's trusted public JWK Set for private_key_jwt client
authentication (RFC 7523 / OpenID Connect Core §9). Returns nil for a
client that does not authenticate with a signed assertion.
Whether the client is an installed native application (RFC 8252 / BCP 212).
A native app runs on the end user's own device — an iOS/Android app or a
desktop binary — rather than on a server the operator controls. That single
fact drives the RFC 8252 authorization-server obligations: PKCE is required
for it (§8.1), it must authenticate at the token endpoint with none because
it cannot hold a secret confidentially (§8.4), and — only when the host also
enables native_apps: [loopback_redirect: true] — its loopback redirect URI
may vary in port (§7.3).
Returns false when the callback is not exposed, so a host that has not
classified its clients gets no RFC 8252 behavior at all.
Marking a client native is mostly additive hardening, with two consequences worth stating outright:
- It is what the loopback redirect exception (§7.3) keys on, but that
relaxation additionally requires the host to enable
native_apps: [loopback_redirect: true]. - Where no
client_public?/1callback is configured at all, a native client counts as public — which both refuses its secret (§8.4) and admits it on the secretlessnonepath. That is the §8.1/§8.4 posture for a native app, but it does mean marking a client native can opennonefor it in a deployment that classifies nothing. A host that wants the per-instance credential carve-out must say so with an explicitclient_public?/1returningfalse.
Note that a native public client cannot use the Pushed Authorization Request
endpoint: PAR refuses secretless clients, and §8.4 refuses this one a secret.
A deployment that sets require_pushed_authorization_requests: true therefore
cannot also serve native public clients.
The client's registered post_logout_redirect_uris (OpenID Connect
RP-Initiated Logout 1.0 §2). The end-session endpoint exact-matches the
request post_logout_redirect_uri against this set; a client exposing none
has no validated return URI (fail closed — the OP renders its own page).
Whether the client may authenticate without a secret and rely on PKCE (RFC 6749 §2.1 / RFC 7636).
The client's registered redirect URIs (RFC 6749 §3.1.2.2). The authorization
endpoint exact-matches the request redirect_uri against this set
(RFC 6749 §3.1.2.3); a client exposing none rejects every authorization
request (fail closed).
Whether the client requires DPoP-bound token issuance (RFC 9449).
Whether the client requires mTLS-bound token issuance (RFC 8705).
@callback load_client(client_id :: String.t()) :: {:ok, client()} | {:error, :not_found} | {:error, :revoked}
Resolve an OAuth client by its identifier (RFC 6749 §2.2).
Returns {:ok, client} for a usable client, {:error, :not_found} when no
such client exists, or {:error, :revoked} when the client is known but has
been revoked. The host owns the registry and the revocation policy.
Constant-time verification of a presented client secret (RFC 6749 §2.3.1).
Returns true iff presented_secret matches the client's stored secret.
The host owns secret hashing; use Attesto.SecureCompare to avoid timing
leaks.