# Forward-Auth Proxy Guides [`/auth/api/forward`](../api/forward.md) · [Trusted headers](../Headers.md) These guides show how to protect a backend application with Paskia using the forward-auth (also called "external authentication") mechanism. The reverse proxy asks Paskia whether a request is allowed before forwarding it to the protected service. For details about the endpoint the proxy calls, see [`/auth/api/forward`](../api/forward.md). For the headers your backend receives on successful requests, see [Trusted Headers](../Headers.md). ## Available guides - [Caddy](caddy.md) — fully supported with ready-to-use snippets (`auth/setup` and `auth/require`). - [Nginx](nginx.md) — using the `auth_request` module. - [Traefik](traefik.md) — using the `ForwardAuth` middleware. - [Apache APISIX](apisix.md) — using the `forward-auth` plugin. - [Envoy](envoy.md) — using the `ext_authz` HTTP filter. - [HAProxy](haproxy.md) — using a Lua auth request. ## Common requirements No matter which proxy you use, the auth subrequest must: 1. Be sent to `GET /auth/api/forward` on the Paskia backend. By default Paskia listens on `localhost:4401`; set the `AUTH_UPSTREAM` environment variable in our Caddy snippets, or point your proxy at wherever Paskia is running. 2. Include the query parameters Paskia needs for access control: - `perm` — required permission scope, repeatable (e.g. `perm=myapp:login`). See [perm argument](../api/perm.md). - `max_age` — how recently the user must have authenticated (e.g. `max_age=5min`). See [max_age argument](../api/max-age.md). - `public=1` — optional; allow public access (anonymous visitors and users missing `perm` pass through, marked with a `Remote-Public` header instead of a 401/403). See [public access](../api/forward.md#public-access). 3. Forward these request headers from the original client request: - `Host` — the site the user is visiting. - `Cookie` — the session cookie, normally `__Host-paskia`. - `X-Forwarded-Method` — the original HTTP method (e.g. `GET`, `POST`). - `X-Forwarded-Uri` — the original path and query string (e.g. `/reports?foo=bar`). - `Accept` — decides whether a 401/403 response should be HTML (browser) or JSON (API/fetch). 4. Strip hop-by-hop headers (`Connection`, `Upgrade`, `Transfer-Encoding`, `Keep-Alive`, `Proxy-Connection`, `TE`) from the auth subrequest. The auth check is a plain HTTP request and must not carry WebSocket/body framing headers. 5. On a `204 No Content` response, copy the `Remote-*` response headers to the request that is forwarded to the protected backend. The headers are the whole point of the auth check. With `public=1`, also copy `Remote-Public` — it marks whether the request is `authenticated`, `forbidden` or `anonymous`, and the backend must check it. 6. On a 401/403 response, send Paskia's response back to the client without contacting the protected backend. (With `public=1` these only occur for reauth requirements.) 7. Also proxy the `/auth/` path prefix to Paskia so the login/profile UI, API endpoints, and WebSockets are reachable. Paskia's WebSocket endpoints need `Upgrade` and `Connection` headers passed through for that path. ## Backend usage After the proxy forwards the request, your backend can read the trusted headers. For example, in Python/FastAPI: ```python user_id = request.headers.get("Remote-User") org_id = request.headers.get("Remote-Org") permissions = request.headers.get("Remote-Groups", "").split(",") ``` Only trust headers that come from the proxy; never trust `Remote-*` headers that arrive directly from the internet. Your proxy configuration should strip any client-supplied `Remote-*` headers before the auth check.