Finish the realm→domain terminology removal across source, tests, e2e and docs. The stored config drops all lists: Config.domains is keyed by rp-id, DomainConfig.origins/related are objects keyed by host (https:// omitted), values True or OriginEntry(auth_host=True). The default/primary domain concept is gone; ordering is display-time. Tests and e2e updated to the new API shapes (not run). Database re-migrated from the legacy backup into the new format.
6.9 KiB
HAProxy Forward-Auth
/auth/api/forward · Proxy guides
HAProxy does not have a built-in forward-auth primitive, but the community haproxy-auth-request Lua script provides an auth-intercept action that works very similarly to Nginx's auth_request. It makes an internal HTTP request to Paskia and copies the response headers to the backend request.
Requirements
- HAProxy 2.2 or newer (2.0+ may work but 2.2+ supports all features shown here).
- Compiled with
USE_LUA=1. - The
haproxy-auth-requestLua script loaded. - The
haproxy-lua-httpdependency in the Lua path.
Overview
global
lua-load /usr/share/haproxy/auth-request.lua
defaults
mode http
timeout connect 5s
timeout client 30s
timeout server 30s
# Backend that runs the Paskia auth check.
backend paskia_auth
server paskia 127.0.0.1:4401
# Backend that runs the Paskia UI / WebSocket / API.
backend paskia_ui
server paskia 127.0.0.1:4401
# Your protected application.
backend app_backend
server app 127.0.0.1:3000
frontend app
bind *:80
# 1. Route /auth/ straight to Paskia, bypassing the auth check.
acl is_auth path_beg /auth/
use_backend paskia_ui if is_auth
# 2. Add the headers Paskia logs / expects. These are then copied to the
# auth subrequest by auth-intercept.
http-request set-header X-Forwarded-Method %[method]
http-request set-header X-Forwarded-Uri %[url]
# 3. Run the auth check. The parameters are:
# backend path method
# req-headers success-headers failure-headers
#
# - req-headers: headers copied from client to Paskia.
# - success-headers: headers copied from Paskia response to backend request.
# - failure-headers: headers copied from Paskia response to client response.
http-request lua.auth-intercept paskia_auth /auth/api/forward?perm=myapp:login GET Host,Cookie,Accept,X-Forwarded-Method,X-Forwarded-Uri Remote-* *
# 4. If the subrequest was not successful, deny the request.
http-request deny if ! { var(txn.auth_response_successful) -m bool }
default_backend app_backend
What the configuration does
- Route
/auth/to Paskia — users must be able to reach the login/profile UI without already being authenticated. HAProxy will proxy WebSocket upgrade headers automatically for this backend when the client requests them. - Set
X-Forwarded-MethodandX-Forwarded-Uri— HAProxy adds these headers to the incoming request so the Lua script can copy them to the auth subrequest.%[method]returns the HTTP method and%[url]returns the path and query string. lua.auth-intercept— sends aGETrequest to/auth/api/forward?perm=myapp:loginon thepaskia_authbackend. It copies the listed request headers (including the dynamic ones we just set) to the auth subrequest.- On success (
2xx) — copies every response header matchingRemote-*from Paskia to the backend request. This overrides any client-suppliedRemote-*headers, so the backend can trust them. - On failure (
4xx) — copies all response headers (*) to the client response and uses Paskia's response body, so the browser gets the login HTML or the JSON auth URL. - Deny if auth failed — the final
http-request denyrule is a safety net. In practice,auth-interceptwith*as the failure-headers already terminates the transaction with Paskia's response.
Backend definition for the auth subrequest
The paskia_auth backend can be the same physical server as paskia_ui, but using a separate backend is convenient because the Lua script will use the first available server in the backend. The auth subrequest is a plain HTTP request, so no special WebSocket options are needed here.
backend paskia_auth
server paskia 127.0.0.1:4401
Per-route permissions
You can run different auth checks for different paths by using HAProxy ACLs. Place the more specific rules before the generic one:
frontend app
bind *:80
acl is_auth path_beg /auth/
use_backend paskia_ui if is_auth
acl is_reports path_beg /reports
http-request set-header X-Forwarded-Method %[method] if is_reports
http-request set-header X-Forwarded-Uri %[url] if is_reports
http-request lua.auth-intercept paskia_auth /auth/api/forward?perm=myapp:reports&max_age=5min GET Host,Cookie,Accept,X-Forwarded-Method,X-Forwarded-Uri Remote-* * if is_reports
http-request set-header X-Forwarded-Method %[method]
http-request set-header X-Forwarded-Uri %[url]
http-request lua.auth-intercept paskia_auth /auth/api/forward?perm=myapp:login GET Host,Cookie,Accept,X-Forwarded-Method,X-Forwarded-Uri Remote-* *
http-request deny if ! { var(txn.auth_response_successful) -m bool }
default_backend app_backend
See perm argument and max_age argument for query parameter syntax.
Public access
For routes where anonymous visitors are allowed but logged-in users should still be identified, add public=1 to the auth subrequest path:
http-request lua.auth-intercept paskia_auth /auth/api/forward?public=1 GET Host,Cookie,Accept,X-Forwarded-Method,X-Forwarded-Uri Remote-* *
# or with a permission the backend will check itself:
http-request lua.auth-intercept paskia_auth /auth/api/forward?public=1&perm=myapp:reports GET Host,Cookie,Accept,X-Forwarded-Method,X-Forwarded-Uri Remote-* *
The Remote-* success-headers glob already copies the Remote-Public header that marks each request as anonymous, forbidden or authenticated. With public=1 the backend always runs and must check Remote-Public before treating the request as authorized; only reauth (max_age) still returns the 401 auth flow, so the http-request deny safety net simply never triggers on public routes. See public access and trusted headers.
Notes
- The Lua script strips the request body from the auth subrequest, so Paskia's
/auth/api/forwardwill only see the headers. - HAProxy variables are limited to alphanumeric characters, dots, and underscores, but the script already normalizes header names for you (e.g.
Remote-Userbecomesreq.auth_response_header.remote_user). TheRemote-*glob pattern in the success-headers argument handles this automatically. - The auth backend must be reachable without TLS. If you need TLS to Paskia, run a local TCP forwarder or use HAProxy's Lua HTTP support directly (not covered by this script).
- If you use a dedicated authentication host (the domain's auth-host setting), route
auth.example.comto the Paskia backend instead of exposing/auth/on every site.