Header Authentication
Header authentication lets Dashy trust a reverse proxy in front of it to handle login. The proxy authenticates the user, then passes their username to Dashy in an HTTP header. Dashy reads that header and signs them in automatically, so there's no separate Dashy login page.
Use this when you already run something like Authelia, Authentik, Traefik's forwardAuth, Caddy's forward_auth, or nginx's auth_request in front of your services, and you want Dashy to pick up the same session.
Contents
- Configure Dashy
- Configure your proxy
- Logging out
- Example: oauth2-proxy and nginx
- Troubleshooting
- Security notes
- How it works
Configure Dashy
In /user-data/conf.yml, set the auth block under appConfig:
appConfig:
auth:
enableHeaderAuth: true
users:
- user: alice
hash: 0a7b1d4c2e... # SHA-256 hash, see below
type: admin
- user: bob
hash: 3f8e2b1a9d...
type: normal
headerAuth:
userHeader: Remote-User
proxyWhitelist:
- 172.18.0.2
enableHeaderAuth- turns the mode onuserHeader- the header holding the username. Defaults toRemote-User. Authelia sendsRemote-User, Authentik sendsX-authentik-username; use whatever yours forwards. The name is case-insensitiveproxyWhitelist- the IP(s) Dashy will accept the header from. Anything not on this list is rejected, which is what stops a client from setting the header itself. Required in practice: if it's empty, nothing gets throughusers- the people allowed in. The forwarded username is matched (case-insensitive) against this list to find theirtype(adminornormal). They still need an entry here even though the proxy did the actual auth
The hash is a SHA-256 of any string. Nobody types it, since the proxy already logged them in, but Dashy uses it to derive the session token it keeps in the browser, so each user needs one. Generate it the same way as for built-in auth.
Configure your proxy
Two things the proxy has to do:
- Forward the username header on every request to Dashy (e.g.
Remote-User: alice). On most forward-auth setups this is a one-line setting - Connect to Dashy from an IP that's in
proxyWhitelist
The whitelist is the security boundary, so Dashy must only be reachable through the proxy. If someone can hit Dashy directly and their IP happens to be whitelisted, they can forge the header. See Security notes.
Logging out
Dashy's logout button only clears Dashy's own session. Your session at the proxy stays alive, so the next page load signs you straight back in. To end both, point logoutRedirectUrl at your proxy's sign-out endpoint:
appConfig:
auth:
logoutRedirectUrl: https://dashy.example.com/oauth2/sign_out
Logging out then sends the browser to that URL, where the proxy can destroy its session.
The specific endpoint depends on your proxy, but for oauth2-proxy it's usually /oauth2/sign_out with an rd query param to chain your identity provider's logout, e.g. /oauth2/sign_out?rd=https://sso.example.com/logout (the rd domain must be in oauth2-proxy's whitelist_domains).
Example: oauth2-proxy and nginx
The following example was from @vmario89 (in #2233).
oauth2-proxy handles login against any OIDC or OAuth2 provider (Synology SSO here). nginx checks each request against its /oauth2/auth endpoint, then forwards the username to Dashy as X-Remote-User.
Dashy config, on top of the setup above. The whitelist is loopback because nginx proxies to Dashy on 127.0.0.1:4000:
appConfig:
auth:
logoutRedirectUrl: https://dashy.example.com/oauth2/sign_out?rd=https://sso.example.com/logout
headerAuth:
userHeader: X-Remote-User
proxyWhitelist:
- 127.0.0.1
- '::1'
- '::ffff:127.0.0.1'
oauth2-proxy config
provider = "oidc"
oidc_issuer_url = "https://login.synology.nas/webman/sso"
oidc_jwks_url = "https://login.synology.nas/webman/sso/openid-jwks.json"
scope = "openid profile email"
oidc_email_claim = "sub"
client_id = "<client-id>"
client_secret = "<client-secret>"
cookie_secret = "<secret>" # openssl rand -base64 32
cookie_name = "cookie_dashy"
cookie_domains = ".example.com"
cookie_secure = true
email_domains = [ "*" ]
http_address = "127.0.0.1:4180"
upstreams = [ "static://200" ]
set_xauthrequest = true
whitelist_domains = [ "dashy.example.com", "login.synology.nas" ]
And a systemd unit to run it:
[Unit]
Description=OAuth2 Proxy (Dashy)
After=network.target
[Service]
User=oauth2proxy
Group=oauth2proxy
ExecStart=/opt/oauth2-proxy/oauth2-proxy --config=/etc/oauth2-proxy/dashy.cfg --trusted-proxy-ip=127.0.0.1/32
Restart=always
RestartSec=10
[Install]
WantedBy=multi-user.target
The load-bearing options:
set_xauthrequest = true- returns the username on auth responses (X-Auth-Request-User) for nginx to pick upupstreams = [ "static://200" ]- oauth2-proxy only answers auth checks here; nginx proxies to Dashy itselfwhitelist_domains- must include therd=logout domain, or the redirect is dropped--trusted-proxy-ip- makes oauth2-proxy trust theX-Forwarded-*headers nginx sets
nginx site config
map $auth_user $auth_user_local {
"" "";
~^(?<lp>[^@]+)@.* $lp;
default $auth_user;
}
server {
server_name dashy.example.com;
# TLS config here
location / {
auth_request /oauth2/auth;
auth_request_set $auth_user $upstream_http_x_auth_request_user;
proxy_set_header X-Remote-User $auth_user_local;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
error_page 401 = /oauth2/sign_in;
proxy_pass http://127.0.0.1:4000;
}
location /oauth2/ {
proxy_pass http://127.0.0.1:4180;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Scheme $scheme;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Auth-Request-Redirect $request_uri;
proxy_set_header Content-Length "";
proxy_pass_request_body off;
}
}
auth_request_set reads the username from oauth2-proxy's response, and the map strips @domain from it, since oauth2-proxy forwards an email but Dashy matches the bare names in auth.users.
Troubleshooting
401 "Unauthorized - not from trusted proxy"
The request reached Dashy from an IP that isn't in proxyWhitelist. The check is on the direct connection IP, so with Docker that's the proxy container's address on the shared network, not the host or the client's IP. Find it with docker inspect <proxy-container> (or read the rejected IP from Dashy's logs) and add it.
401 "Unauthorized - missing user header"
The request came from a trusted IP but had no username header. Either the proxy isn't forwarding it, or userHeader doesn't match the header name the proxy actually sends. Check the exact header in your proxy config.
"User '...' from upstream proxy was not found in conf.yml"
The proxy sent a username with no matching entry in auth.users. Add a user whose user matches it (case doesn't matter).
Logged in but can't save the config
That user's type isn't admin. Saving is admin-only, so set type: admin on their entry.
Logging out just logs me back in
Expected with the default config. Logout clears Dashy's cookie, but you're still signed in at the proxy, so the next page load re-authenticates from the header. Set logoutRedirectUrl to your proxy's sign-out endpoint to end both sessions — see Logging out.
Security notes
- Header auth is only as strong as the proxy in front of it. Dashy trusts any whitelisted IP that sends the header, so the whole model depends on Dashy being unreachable except through the proxy. Lock that down at the network or firewall level
- Keep
proxyWhitelisttight: just the proxy's real connecting IP(s), nothing broader - The username and role come from
conf.yml. A user the proxy authenticates but that you haven't listed is refused, so removing someone fromauth.usersblocks them even if the proxy still lets them through
How it works
- The user hits Dashy through the proxy. The proxy authenticates them and adds the username header
- On load, Dashy's frontend calls
/get-user. The server checks the request IP againstproxyWhitelist, and if it's trusted, reads the username fromuserHeaderand returns it - The frontend looks that username up in
auth.users, derives a session token from the user and hash, sets the auth cookie, and stores the username - From there it's normal Dashy auth:
isLoggedIn, the admin check, and the per-page, section and item visibility rules all work as usual
Server-side, the same proxy-whitelist middleware guards the config and API routes, so a request from an untrusted IP is rejected before it reaches anything. Writes (saving config) additionally require the matched user to be type: admin.
The relevant code is src/utils/auth/HeaderAuth.js on the frontend, and services/app.js with services/get-user.js on the server.