- Serve multiple domains (RP IDs) from one instance: host-based dispatch, per-domain credentials and sessions, domains managed at runtime in the admin UI — previously one RP per instance - Cross-domain sign-in via Related Origin Requests: per-domain related-origins list with a served .well-known/webauthn document - Explicit per-domain origin lists with shell-glob wildcards (**. for apex + any subdomain depth, *. for one level), editable in the admin UI with validation and self-lockout guards - Per-domain auth hosts: the account/admin UI can live on a different host per domain, no longer confined to subdomains of a single RP - CLI: 'paskia init <rp-id [rp-name]' initializes or adds a domain to an existing database; 'paskia migrate' converts legacy databases BREAKING CHANGES (v2.0): - Database schema: config is now per-domain and credentials/sessions carry an rp_id — existing databases must be converted with 'paskia migrate' - Origins are now explicit: main implicitly allowed every subdomain of the RP; configure '**.' origins to reproduce that behavior - CLI: the flat '--rp-id/--rp-name/--origin/--auth/--save' flags are replaced by the 'init' and 'migrate' subcommandsReviewed-on: #4
139 lines
5.7 KiB
Markdown
139 lines
5.7 KiB
Markdown
# Traefik ForwardAuth
|
|
|
|
[`/auth/api/forward`](../api/forward.md) · [Proxy guides](index.md)
|
|
|
|
This guide uses Traefik's [ForwardAuth middleware](https://doc.traefik.io/traefik/reference/routing-configuration/http/middlewares/forwardauth/) to ask Paskia whether each request is allowed.
|
|
|
|
## Overview
|
|
|
|
A typical dynamic (YAML) configuration looks like this:
|
|
|
|
```yaml
|
|
http:
|
|
routers:
|
|
app:
|
|
rule: "Host(`app.example.com`)"
|
|
service: app-backend
|
|
middlewares:
|
|
- paskia-auth
|
|
|
|
# Route /auth/ straight to Paskia, bypassing the auth middleware.
|
|
auth:
|
|
rule: "Host(`app.example.com`) && PathPrefix(`/auth/`)"
|
|
service: paskia
|
|
middlewares: []
|
|
|
|
middlewares:
|
|
paskia-auth:
|
|
forwardAuth:
|
|
address: "http://localhost:4401/auth/api/forward?perm=myapp:login"
|
|
# Forward every Remote-* header from the auth response to the backend.
|
|
authResponseHeadersRegex: "^Remote-"
|
|
# Explicitly pass the headers Paskia needs. If left empty, all headers
|
|
# are forwarded; being explicit avoids accidentally leaking hop-by-hop
|
|
# headers to the auth server.
|
|
authRequestHeaders:
|
|
- Host
|
|
- Cookie
|
|
- Accept
|
|
- X-Forwarded-Method
|
|
- X-Forwarded-Uri
|
|
- X-Forwarded-Host
|
|
- X-Forwarded-Proto
|
|
- X-Forwarded-For
|
|
|
|
services:
|
|
app-backend:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://localhost:3000"
|
|
|
|
paskia:
|
|
loadBalancer:
|
|
servers:
|
|
- url: "http://localhost:4401"
|
|
```
|
|
|
|
## What Traefik sends automatically
|
|
|
|
Traefik's ForwardAuth middleware sends the auth request to the configured `address` and includes the following headers derived from the original request:
|
|
|
|
| Header | Value |
|
|
|---|---|
|
|
| `X-Forwarded-Method` | Original HTTP method |
|
|
| `X-Forwarded-Proto` | Original protocol (`http`/`https`) |
|
|
| `X-Forwarded-Host` | Original host |
|
|
| `X-Forwarded-Uri` | Original request URI (path and query) |
|
|
| `X-Forwarded-For` | Client IP address |
|
|
|
|
These are exactly the headers Paskia logs. You should still include them in `authRequestHeaders` if you set that list explicitly, to make sure they are not filtered out.
|
|
|
|
## Response headers
|
|
|
|
`authResponseHeadersRegex: "^Remote-"` tells Traefik to copy every response header starting with `Remote-` from Paskia's `204` response and add it to the request that is forwarded to your backend. It also strips any `Remote-*` headers that the client may have sent, so the backend can trust the values.
|
|
|
|
For stricter control, you can list the headers explicitly instead of using the regex:
|
|
|
|
```yaml
|
|
authResponseHeaders:
|
|
- Remote-User
|
|
- Remote-Name
|
|
- Remote-Groups
|
|
- Remote-Org
|
|
- Remote-Org-Name
|
|
- Remote-Role
|
|
- Remote-Role-Name
|
|
- Remote-Session-Expires
|
|
- Remote-Credential
|
|
- Remote-Public
|
|
```
|
|
|
|
## Proxying `/auth/` to Paskia
|
|
|
|
The `/auth/` router above forwards all authentication UI, API, and WebSocket traffic to Paskia. Because this router does **not** use the `paskia-auth` middleware, users can reach the login page and profile UI without being authenticated first. Traefik handles WebSocket upgrades automatically when the client requests them.
|
|
|
|
If you are using a dedicated authentication host instead of `/auth/`, create a separate router for `auth.example.com` pointing to the Paskia service and set the domain's auth host in the admin panel's Domains section.
|
|
|
|
## Adjusting requirements
|
|
|
|
Change the `address` query string to require different permissions or recent authentication:
|
|
|
|
```yaml
|
|
address: "http://localhost:4401/auth/api/forward?perm=myapp:login"
|
|
address: "http://localhost:4401/auth/api/forward?perm=myapp:admin&max_age=5min"
|
|
address: "http://localhost:4401/auth/api/forward"
|
|
```
|
|
|
|
The last form requires only authentication, no specific permission. See [perm argument](../api/perm.md) and [max_age argument](../api/max-age.md).
|
|
|
|
## Docker labels example
|
|
|
|
When using Traefik with Docker Compose, you can define the middleware with labels:
|
|
|
|
```yaml
|
|
labels:
|
|
- "traefik.enable=true"
|
|
- "traefik.http.routers.myapp.rule=Host(`app.example.com`)"
|
|
- "traefik.http.routers.myapp.middlewares=paskia-auth"
|
|
- "traefik.http.middlewares.paskia-auth.forwardauth.address=http://localhost:4401/auth/api/forward?perm=myapp:login"
|
|
- "traefik.http.middlewares.paskia-auth.forwardauth.authResponseHeadersRegex=^Remote-"
|
|
- "traefik.http.middlewares.paskia-auth.forwardauth.authRequestHeaders=Host,Cookie,Accept,X-Forwarded-Method,X-Forwarded-Uri"
|
|
```
|
|
|
|
## Public access
|
|
|
|
For routes where anonymous visitors are allowed but logged-in users should still be identified, add `public=1` to the middleware `address`:
|
|
|
|
```yaml
|
|
address: "http://localhost:4401/auth/api/forward?public=1"
|
|
address: "http://localhost:4401/auth/api/forward?public=1&perm=myapp:reports"
|
|
```
|
|
|
|
The auth check then always returns 204 (except reauth with `max_age`, which still returns the 401 auth flow), and the `Remote-Public` header — copied by `authResponseHeadersRegex: "^Remote-"` — marks each request as `anonymous`, `forbidden` or `authenticated`. The backend always runs and must check `Remote-Public` before treating the request as authorized. See [public access](../api/forward.md#public-access) and [trusted headers](../Headers.md#public-access).
|
|
|
|
## Notes
|
|
|
|
- By default ForwardAuth sends a request without the original body. If you need to forward the body for logging/validation, set `forwardBody: true` and a sensible `maxBodySize`, but for Paskia this is not required.
|
|
- Paskia does not set cookies on the forward-auth response; it only returns `Remote-*` headers. Use the JavaScript helpers from the [paskia](https://www.npmjs.com/package/paskia) package for session renewal in the frontend.
|
|
- For HTTPS, use `https://` in the `address` and configure TLS options (`tls.insecureSkipVerify: true` only for testing).
|