# `AttestoPhoenix.AuthorizationServer.PAR`
[🔗](https://github.com/XukuLLC/attesto_phoenix/blob/v0.19.0/lib/attesto_phoenix/authorization_server/par.ex#L1)

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.

# `dpop_input`

```elixir
@type dpop_input() :: %{
  optional(:proofs) =&gt; [String.t()],
  optional(:http_uri) =&gt; String.t() | nil,
  optional(:http_method) =&gt; 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.

# `store`

```elixir
@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.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
