AttestoPhoenix.AuthorizationServer.PAR (AttestoPhoenix v0.7.1)

Copy Markdown View Source

Pushed Authorization Request storage (RFC 9126), as conn-free core.

This is the single place that turns an authenticated client and a parsed authorization request into either a stored request_uri reference or an AttestoPhoenix.OAuthError. The Pushed Authorization Request endpoint (RFC 9126) authenticates the client (RFC 6749 §2.3), stores the submitted authorization request parameters behind a request_uri, and returns that reference to be used at the authorization endpoint, which still performs the normal client/redirect/scope/PKCE validation when the reference is resolved.

North star

AttestoPhoenix.Controller.PARController parses the request off the Plug.Conn, authenticates the client via AttestoPhoenix.ClientAuthentication (RFC 6749 §2.3), lifts the DPoP facts into a %Request{} of plain data, and calls store/2. This module reads only data, never touches a conn, and never emits an event. Policy is carried on the %AttestoPhoenix.Config{} the caller passes in (the :par_store persistence, the :par_ttl, the host callbacks); nothing is hardcoded here.

Return value

{:ok, %{request_uri: request_uri, expires_in: ttl}} on success, where request_uri is a freshly generated urn:ietf:params:oauth:request_uri: reference (RFC 9126 §2.2) and ttl is the configured lifetime in seconds. {:error, %AttestoPhoenix.OAuthError{}} on failure.

Security details preserved

  • The stored record carries the authenticated client_id resolved through the host's :client_id callback (RFC 6749 §2.2), overriding any body-supplied value. When no :client_id callback is configured the request's own presented client_id is left intact (not clobbered with nil); the library makes no assumption about the opaque client shape. The client-authentication credentials (client_secret, client_assertion, client_assertion_type) are dropped before storage.
  • RFC 9449: when a DPoP proof is presented at the PAR endpoint, it is verified against the canonical request URL/method (RFC 9449 §4.2 / §4.3) with the configured replay check, and its jkt is stored as the dpop_jkt the authorization code will later be sender-constrained to. A submitted dpop_jkt request parameter that disagrees with the verified proof's thumbprint is rejected (invalid_dpop_proof). Presenting more than one DPoP proof is rejected (RFC 9449 §4.1). A dpop_jkt parameter submitted without a proof is honoured as-is, since the proof of possession is demonstrated later at the token endpoint.

Summary

Types

The conn-free DPoP facts the controller lifts off the PAR request (RFC 9449 §4.1 / §4.2 / §4.3).

Functions

Store a pushed authorization request, returning the request_uri reference and its lifetime, or an error.

Types

dpop_input()

@type dpop_input() :: %{
  optional(:proofs) => [String.t()],
  optional(:http_uri) => String.t() | nil,
  optional(:http_method) => String.t() | nil
}

The conn-free DPoP facts the controller lifts off the PAR request (RFC 9449 §4.1 / §4.2 / §4.3).

  • :proofs - the DPoP request-header values (Plug.Conn.get_req_header(conn, "dpop")); [] when no proof was presented, more than one entry being a rejected ambiguous request.
  • :http_uri - the canonical request URL (htu) the proof is bound to.
  • :http_method - the HTTP method (htm) the proof is bound to.

Functions

store(config, request)

@spec store(
  AttestoPhoenix.Config.t(),
  AttestoPhoenix.AuthorizationServer.PAR.Request.t()
) ::
  {:ok, %{request_uri: String.t(), expires_in: pos_integer()}}
  | {:error, AttestoPhoenix.OAuthError.t()}

Store a pushed authorization request, returning the request_uri reference and its lifetime, or an error.

config is the validated %AttestoPhoenix.Config{} carrying the :par_store persistence, the :par_ttl, and the host callbacks; request is the AttestoPhoenix.AuthorizationServer.PAR.Request the controller built from the authenticated client, the request body, and the conn-free DPoP facts. See the module docs for the return shape and the security details preserved.