# `AttestoPhoenix.RequestContext`
[🔗](https://github.com/XukuLLC/attesto_phoenix/blob/v0.19.0/lib/attesto_phoenix/request_context.ex#L1)

Neutral request-fact helpers the OAuth 2.0 / OIDC flows derive from a `Plug.Conn`.

Authorization-server endpoints need a handful of transport-level facts that are
not safe to read straight off the `Plug.Conn` when the listener sits behind a
reverse proxy:

  * the **client IP**, honoring `X-Forwarded-For` only for trusted proxies;
  * whether the request effectively arrived over **HTTPS** (RFC 8446), honoring
    a trusted `X-Forwarded-Proto: https` hop;
  * the canonical request **URL** (`htu`) and **method** (`htm`) a DPoP proof is
    bound to, per RFC 9449 §4.2 / §4.3;
  * the peer **certificate DER** presented at the TLS layer, used for the
    RFC 8705 §3 mutual-TLS `cnf` binding.

Every forwarded-header-derived fact is gated on a trusted-proxy allowlist. A
request that arrives from a peer outside that allowlist with forged
`X-Forwarded-*` headers is a spoofing attempt: the headers are dropped and the
fact is derived from the direct connection only. This is fail-closed by
construction, an untrusted peer cannot assert `https`, cannot redirect the
DPoP `htu`, and cannot forge a client IP.

The trust boundary, the HTTPS requirement, and the optional certificate
extractor are read from `AttestoPhoenix.Config`; this module never hardcodes
deployment policy.

## Trusted-proxy allowlist

`config.trusted_proxies` controls whether `X-Forwarded-*` headers are honored.
It accepts a list whose elements are any of:

  * `:loopback` - matches `127.0.0.0/8` and `::1`.
  * `:any` - matches every peer. Only safe when another mechanism (firewall,
    ingress ACL) guarantees that only the proxy can reach the app port. Prefer
    explicit CIDRs.
  * an IP tuple (`{10, 0, 0, 1}` / an 8-element IPv6 tuple) - exact match.
  * a binary CIDR string (`"10.0.0.0/8"`, `"::1/128"`) - subnet match.

The default (`[]`) trusts no proxy, so forwarded headers are never honored
unless the host opts in.

# `canonical_url`

```elixir
@spec canonical_url(Plug.Conn.t(), AttestoPhoenix.Config.t()) :: String.t()
```

Returns the canonical request URL (`htu`) the DPoP proof is bound to, per
RFC 9449 §4.3: the request URI without its query or fragment.

When `config.htu` is set, that callback is used so a host can fully override
URL reconstruction (e.g. for a proxy topology this module does not model).
Otherwise the URL is built from the effective scheme/host/port, which honor
`X-Forwarded-Proto` / `X-Forwarded-Host` / `X-Forwarded-Port` only when the
request comes from a trusted proxy. An untrusted peer therefore cannot
redirect the `htu` check by injecting forwarded headers: the URL falls back to
the direct connection's authority and the proof either verifies against the
real listener URL or fails on its signature.

# `cert_der`

```elixir
@spec cert_der(Plug.Conn.t(), AttestoPhoenix.Config.t()) :: binary() | nil
```

Returns the peer certificate DER for the RFC 8705 §3 mutual-TLS `cnf` binding,
or `nil` when no client certificate was presented.

When `config.cert_der` is set (required by `AttestoPhoenix.Config` whenever
`mtls_enabled` is true), that callback extracts the DER; this is the supported
override for proxy topologies that surface the client certificate in a header
rather than on the TLS socket. Otherwise the certificate is read from the
connection's peer data, which the underlying adapter populates when the TLS
socket negotiated client authentication.

# `check_https`

```elixir
@spec check_https(Plug.Conn.t(), AttestoPhoenix.Config.t()) ::
  :ok | {:error, :insecure_transport}
```

Returns `:ok` when the request satisfies the configured transport policy, or
`{:error, :insecure_transport}` when `config.require_https` is set and the
request did not effectively arrive over HTTPS.

This is the fail-closed transport check the token and protected-resource
endpoints run before touching a credential: a bearer token or client secret
that has already crossed a plain-HTTP hop must be treated as compromised, so
the request is refused rather than served or redirected (a redirect would have
the client replay the exposed credential).

# `client_ip`

```elixir
@spec client_ip(Plug.Conn.t(), AttestoPhoenix.Config.t()) :: String.t() | nil
```

Returns the client IP as a string, or `nil` if it cannot be determined.

When the request comes from a trusted proxy and carries `X-Forwarded-For`, the
left-most entry (the original client per RFC 7239 / the de-facto
`X-Forwarded-For` convention) is returned. Otherwise the direct connection's
`remote_ip` is used. An untrusted peer cannot forge the client IP this way: its
`X-Forwarded-For` is ignored entirely.

# `from_trusted_proxy?`

```elixir
@spec from_trusted_proxy?(Plug.Conn.t(), AttestoPhoenix.Config.t()) :: boolean()
```

Returns `true` when `conn.remote_ip` falls inside `config.trusted_proxies`.

This is the single trust gate that governs whether any `X-Forwarded-*` header
is honored. It is exposed so callers that need a custom forwarded-header read
can apply the same boundary rather than re-implementing it and risking drift.

# `http_method`

```elixir
@spec http_method(Plug.Conn.t()) :: String.t()
```

Returns the HTTP method (`htm`) the DPoP proof is bound to, per RFC 9449 §4.2.

The method is taken verbatim from the request; it is not derived from any
forwarded header.

# `https?`

```elixir
@spec https?(Plug.Conn.t(), AttestoPhoenix.Config.t()) :: boolean()
```

Returns `true` when the request effectively arrived over HTTPS.

The effective scheme is the connection scheme, upgraded to `https` when a
trusted proxy forwards `X-Forwarded-Proto: https`. An untrusted peer's
forwarded scheme is ignored, so a plain-HTTP hop cannot masquerade as TLS.

---

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