The proxy auth docs never told operators that the identity and role headers must be replaced rather than appended, nor that Pulse has to be unreachable except through the proxy. Both are prerequisites for the scheme being safe at all, and neither is enforceable from inside Pulse. The append case is the sharp edge: Pulse reads the first value of a repeated header, so a client-supplied role header that arrives ahead of the proxy's value decides the admin verdict. The client does not need the shared secret to do it, because the proxy attaches the secret itself. Verified against a scratch instance: sending "X-Proxy-Roles: user" then "admin" yields 403 while "admin" then "user" yields 200 on /api/system/settings. Documented rather than fixed in code on purpose. Matching any value instead of the first would make injection strictly easier, and rejecting repeated headers outright would break identity providers that legitimately emit one header per group. The trust boundary is the proxy's to hold.
4.2 KiB
🛡️ Proxy Authentication
Authenticate users via your existing reverse proxy (Authentik, Authelia, Cloudflare Zero Trust, etc.).
🚀 Quick Start
- Generate Secret: Create a strong random string.
- Configure Pulse:
PROXY_AUTH_SECRET=your-random-secret PROXY_AUTH_USER_HEADER=X-Authentik-Username - Configure Proxy: Set the proxy to send
X-Proxy-Secretand the user header.
⚙️ Configuration
| Variable | Description | Default |
|---|---|---|
PROXY_AUTH_SECRET |
Required. Shared secret to verify requests. | - |
PROXY_AUTH_USER_HEADER |
Required. Header containing the username. | - |
PROXY_AUTH_ROLE_HEADER |
Header containing user groups/roles. | - |
PROXY_AUTH_ROLE_SEPARATOR |
Separator for multiple roles in the header. | | |
PROXY_AUTH_ADMIN_ROLE |
Role name that grants admin access. | admin |
PROXY_AUTH_LOGOUT_URL |
URL to redirect to after logout. | - |
Setting PROXY_AUTH_ROLE_HEADER turns on role gating. From then on admin access fails closed unless the role header is present and contains the admin role — PROXY_AUTH_ADMIN_ROLE if you set it, otherwise the admin default. A missing or blank role header still authenticates the user, but the user is treated as non-admin.
If you intentionally want every proxy-authenticated user to be an admin, leave PROXY_AUTH_ROLE_HEADER unset and protect Pulse entirely at the proxy/IdP layer.
⚠️ Header Trust Boundary
Pulse trusts these headers completely — they are the identity and the privilege decision. Two deployment requirements make that safe, and both are yours to enforce:
- Your proxy must replace these headers, never append to them. On every request the proxy has to discard any client-supplied copy of
X-Proxy-Secret, your user header, and your role header, then set its own. Pulse reads the first value of a repeated header, so a client-suppliedX-Proxy-Roles: adminthat arrives ahead of your proxy's value wins — and because the proxy supplies the shared secret itself, the client never needs to know it. The examples below use nginxproxy_set_headerand TraefikcustomRequestHeaders, which replace; be careful with anything that adds a header rather than setting it. - Pulse must not be reachable except through the proxy. Anyone who can connect directly and knows
PROXY_AUTH_SECRETcan assert any username and any role. Bind Pulse to the proxy's network or to localhost.
Neither of these can be enforced from inside Pulse: a forged header and a genuine one are indistinguishable once they arrive.
📦 Examples
Authentik (with Traefik)
docker-compose.yml:
environment:
- PROXY_AUTH_SECRET=secure-secret
- PROXY_AUTH_USER_HEADER=X-Authentik-Username
Traefik Middleware:
headers:
customRequestHeaders:
X-Proxy-Secret: "secure-secret"
Authelia (Nginx)
location / {
auth_request /authelia;
proxy_set_header X-Proxy-Secret "secure-secret";
proxy_set_header Remote-User $upstream_http_remote_user;
proxy_pass http://pulse:7655;
}
Cloudflare Tunnel
- Zero Trust Dashboard: Applications → Add Application.
- Settings: HTTP Settings → HTTP Headers.
- Add Header:
X-Proxy-Secret=your-secret. - Pulse Config:
PROXY_AUTH_USER_HEADER=Cf-Access-Authenticated-User-Email.
🔧 Troubleshooting
| Issue | Check |
|---|---|
| 401 Unauthorized | Verify X-Proxy-Secret matches PROXY_AUTH_SECRET. Check if headers are being stripped by intermediate proxies. |
| Not Admin | Check the role header actually reaches Pulse and contains the admin role exactly — matching is case-sensitive and compares whole roles, so Admins and authentik Admins do not match the admin default. Set PROXY_AUTH_ADMIN_ROLE to your IdP's admin group name. Pulse logs a warning at startup when it falls back to the default. |
| Logout Fails | Ensure PROXY_AUTH_LOGOUT_URL is set to your IdP's logout endpoint. |
Verify Headers
Use curl to simulate a proxy request:
curl -H "X-Proxy-Secret: your-secret" \
-H "X-Authentik-Username: admin" \
http://localhost:7655/api/state