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.
Summary
Functions
A Plug.Parsers body reader that checks ambiguity before map conversion.
Types
@type reason() :: {:duplicate_parameter, String.t()} | :body_too_large | :invalid_parameter_encoding
Functions
@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).