AttestoPhoenix. AuthorizationServer. SenderConstraint
(AttestoPhoenix v0.7.0)
Copy Markdown
View Source
Sender-constraint resolution for the token endpoint (RFC 9449 / RFC 8705), as conn-free core.
This is the single place that turns the sender-constraint facts of a token
request - a presented DPoP proof (RFC 9449), a presented client certificate
(RFC 8705), and the canonical request URL/method the proof is bound to
(RFC 9449 §4.2 / §4.3) - together with the configured policy and the client's
binding requirements into either a resolved binding or an
AttestoPhoenix.OAuthError. The controller parses these facts off the
Plug.Conn (via AttestoPhoenix.RequestContext and the DPoP request
header) and passes them as a plain map; this module reads only data, never
touches a conn, and never emits an event.
Input
resolve/3 takes the validated %AttestoPhoenix.Config{}, the resolved
client, and an input map the controller builds from the request:
:dpop_proof- the firstDPoPrequest-header value (RFC 9449 §4.1), ornilwhen the request carries no proof.:mtls_cert_der- the peer certificate DER (RFC 8705 §3), ornilwhen no client certificate was presented.:http_uri- the canonical request URL (htu) the proof is bound to (RFC 9449 §4.3).:http_method- the HTTP method (htm) the proof is bound to (RFC 9449 §4.2); the token endpoint is reached by POST.
Return value
{:ok, binding, token_type} where binding is one of {:dpop, jkt},
{:mtls, thumbprint}, or :none, and token_type is the RFC 9449 §7.1 /
RFC 6750 presentation type ("DPoP" for a DPoP binding, "Bearer"
otherwise). On failure, {:error, %AttestoPhoenix.OAuthError{}}.
Precedence and fail-closed policy
DPoP takes precedence when a proof is presented (RFC 9449 §5); otherwise an mTLS certificate binds the token to its thumbprint; otherwise the token is an unbound Bearer - but only if the client does not require a sender constraint.
RFC 8705 §3: a client configured to require certificate-bound tokens MUST NOT
be silently downgraded to a Bearer token when it calls without a certificate.
RFC 9449 is the DPoP equivalent: a client configured for DPoP-bound issuance
must present a proof at the token endpoint. The host's
:client_requires_mtls? / :client_requires_dpop? callbacks gate this; both
are read defensively and fail open only to "not required" when the host has
not supplied the callback (the constraints are off by default per
:dpop_enabled / :mtls_enabled).
DPoP nonce challenge preserved
When a fresh DPoP nonce is required (RFC 9449 §8 / §9), the returned
%AttestoPhoenix.OAuthError{} carries the use_dpop_nonce code and the fresh
DPoP-Nonce value in its :headers, so the controller renders the header
verbatim alongside the error.
Summary
Types
The resolved sender-constraint binding.
The sender-constraint facts the controller derives from the request.
Functions
The DPoP thumbprint a stateful grant (authorization-code redemption, refresh
rotation) binds to. Only DPoP flows through those engines' :dpop_jkt opt;
an mTLS binding carries no DPoP thumbprint.
Whether the client requires DPoP-bound token issuance (RFC 9449).
Whether the client requires certificate-bound token issuance (RFC 8705).
The Attesto.Token.mint/3 confirmation opt for a resolved binding
(RFC 9449 / RFC 8705).
The DPoP thumbprint to bind a refresh token to (RFC 9449 §8).
Resolve the sender-constraint binding for a token request.
Types
The resolved sender-constraint binding.
@type input() :: %{ optional(:dpop_proof) => String.t() | nil, optional(:mtls_cert_der) => binary() | nil, optional(:http_uri) => String.t() | nil, optional(:http_method) => String.t() | nil }
The sender-constraint facts the controller derives from the request.
Functions
The DPoP thumbprint a stateful grant (authorization-code redemption, refresh
rotation) binds to. Only DPoP flows through those engines' :dpop_jkt opt;
an mTLS binding carries no DPoP thumbprint.
@spec client_requires_dpop?(AttestoPhoenix.Config.t(), term()) :: boolean()
Whether the client requires DPoP-bound token issuance (RFC 9449).
Read defensively; fails open to "not required" when the host supplies no
:client_requires_dpop? callback.
@spec client_requires_mtls?(AttestoPhoenix.Config.t(), term()) :: boolean()
Whether the client requires certificate-bound token issuance (RFC 8705).
Read defensively; fails open to "not required" when the host supplies no
:client_requires_mtls? callback.
The Attesto.Token.mint/3 confirmation opt for a resolved binding
(RFC 9449 / RFC 8705).
DPoP binds cnf.jkt; mTLS binds cnf.x5t#S256 (the certificate thumbprint,
threaded so a real cnf is minted rather than dropped); an unbound binding
carries no opt.
@spec refresh_binding_jkt(AttestoPhoenix.Config.t(), term(), binding()) :: String.t() | nil
The DPoP thumbprint to bind a refresh token to (RFC 9449 §8).
Public clients get DPoP-bound refresh tokens; for confidential clients the
refresh token stays bound to the authenticated client_id (RFC 6749 §6 /
§10.4) rather than one DPoP proof key, so no DPoP thumbprint is threaded.
@spec resolve(AttestoPhoenix.Config.t(), input(), term()) :: {:ok, binding(), String.t()} | {:error, AttestoPhoenix.OAuthError.t()}
Resolve the sender-constraint binding for a token request.
Returns {:ok, binding, token_type} or {:error, %OAuthError{}}. See the
module docs for the precedence rules and the input shape.