- 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
5.7 KiB
Traefik ForwardAuth
/auth/api/forward · Proxy guides
This guide uses Traefik's ForwardAuth middleware to ask Paskia whether each request is allowed.
Overview
A typical dynamic (YAML) configuration looks like this:
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:
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:
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 and max_age argument.
Docker labels example
When using Traefik with Docker Compose, you can define the middleware with labels:
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:
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 and trusted headers.
Notes
- By default ForwardAuth sends a request without the original body. If you need to forward the body for logging/validation, set
forwardBody: trueand a sensiblemaxBodySize, 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 package for session renewal in the frontend. - For HTTPS, use
https://in theaddressand configure TLS options (tls.insecureSkipVerify: trueonly for testing).