Files
paskia/docs/proxy/traefik.md
LeoVasanko 84985501f5 MultiSite: one instance serves authentication across many domains (#4)
- 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
2026-09-07 22:14:42 +00:00

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: 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 package for session renewal in the frontend.
  • For HTTPS, use https:// in the address and configure TLS options (tls.insecureSkipVerify: true only for testing).