# `AttestoPhoenix.DuplicateParameterGuard`
[🔗](https://github.com/XukuLLC/attesto_phoenix/blob/v4.2.1/lib/attesto_phoenix/duplicate_parameter_guard.ex#L1)

Preserves and validates OAuth request parameters before Phoenix collapses them.

The raw query string remains available on `Plug.Conn`, so Attesto's protocol
controllers reject repeated query names automatically.
URL-encoded and JSON bodies are different: `Plug.Parsers` converts them to a
map before router dispatch, losing repeated names. Configure this module as
the endpoint's body reader so the controller can inspect the original bytes:

    plug Plug.Parsers,
      parsers: [:urlencoded, :multipart, :json],
      pass: ["*/*"],
      json_decoder: Phoenix.json_library(),
      body_reader: {AttestoPhoenix.DuplicateParameterGuard, :read_body, []}

The reader examines the bytes before `Plug.Parsers` collapses them and returns
every `:more` result unchanged so Plug can enforce its request limit. If
another caller continues reading, the reader retains chunks up to the
configured `:length` so names split across a boundary cannot bypass
validation. It then keeps only the analysis until controller dispatch;
`validate_and_forget/1` removes that metadata before the action runs.

RFC 8707 deliberately permits more than one `resource` parameter. Those
values in URL-encoded input are preserved as a list and passed to Attesto's
resource-indicator validation. Every other repeated top-level form name is
rejected. JSON object names must be unique at every nesting level. Bracketed
form names are compared by their top-level root, so
`client_id=a&client_id[]=b` cannot evade the check.

If the host already uses a custom body reader, pass its MFA as the sole extra
argument:

    body_reader: {
      AttestoPhoenix.DuplicateParameterGuard,
      :read_body,
      [{MyApp.BodyReader, :read_body, []}]
    }

Protocol form or JSON requests without the reader emit
`[:attesto_phoenix, :oauth_body_guard, :missing_analysis]` telemetry with a
count and the HTTP method/body format. Configure `oauth_body_guard: :required`
to reject missing analysis after installing the reader on every endpoint.
Multipart requests also emit this event and are rejected under the required
policy: Plug's multipart parser bypasses the body reader, so it cannot
establish duplicate analysis. Host upload routes can retain multipart parsing.

# `reason`

```elixir
@type reason() ::
  {:duplicate_parameter, String.t()}
  | :body_too_large
  | :invalid_parameter_encoding
```

# `read_body`

```elixir
@spec read_body(Plug.Conn.t(), keyword()) :: tuple()
```

A `Plug.Parsers` body reader that checks ambiguity before map conversion.

It otherwise has the same return contract and read limits as
`Plug.Conn.read_body/2` (or the optional underlying reader MFA).

# `read_body`

```elixir
@spec read_body(Plug.Conn.t(), keyword(), {module(), atom(), list()}) :: tuple()
```

---

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