Auth Modes — who can access what?
Inhalt
Mesh has a pluggable auth system with 4 modes. Each mode is a strategy class in apps/web/src/server/auth/.
Overview
| Mode | Who can access? | Who can use admin routes? | Use case |
|---|---|---|---|
open |
anyone with URL access | everyone | Dev / home network / behind VPN |
token |
anyone with URL access | only with Bearer token header | Home network with admin protection |
userPassword |
after login | admin role only | Family dashboard with auth |
oauth2 |
after OIDC login | group mapping | Production / SSO with Authentik etc. |
Required: MESH_SESSION_SECRET
For all modes except open: MESH_SESSION_SECRET (32+ characters) must be set in the env vars. The secret signs the session cookies (HMAC-SHA256). Always reuse it on container replacement.
Switching modes
Admin → Users & Security → Auth.
⚠ Before switching to userPassword or oauth2:
- Create at least one admin account (Admin → Users → "New User")
- Only then activate the mode
- Otherwise: lockout — fix via editing
/data/config.json+ container restart
Mode open
Default after First Run. No auth, every request is admin. Mesh shows a red warning banner on the dashboard while auth.mode === "open".
Mode token
A single bearer token per Mesh instance (auth.token in config.json). Send via Authorization: Bearer <token> header — browser extension like ModHeader or reverse proxy auth.
Advantage: no login screen, good for API integrations
Disadvantage: poor UX, one token for everyone
Mode userPassword
Classic login with username + bcrypt password.
- Roles:
admin,editor,viewer - Admin routes require
admin - Editor can change content but not auth/users
- Viewer = read only
Password reset: only by directly editing config.json (set a new bcrypt hash) — no self-service UI (may come in v2.0).
Mode oauth2 (OIDC)
Production-ready, tested with Authentik + Keycloak.
Prerequisite: reverse proxy
Mesh runs internally as HTTP on port 3000. If a reverse proxy sits in front (NPM, Traefik, Caddy …), it must set these headers:
X-Forwarded-Proto: https
Host: <mesh-domain>
Mesh uses them to build the public callback URL (https://<domain>/api/auth/oidc/callback). Without the headers the app sends the IdP a http://0.0.0.0:3000/… URL → Redirect URI Error or broken post-login redirect.
NPM, Traefik and Caddy set these headers by default — no extra steps needed. With manual nginx: add
proxy_set_header X-Forwarded-Proto $scheme;andproxy_set_header Host $http_host;in thelocationblock.
Setup in your IdP
-
Create an Application with:
- Redirect URI:
https://<mesh-url>/api/auth/oidc/callback - Scopes:
openid+profile+email - Optional: enable group claim (for role mapping)
- Redirect URI:
-
Note down:
issuer-url,client-id,client-secret
Setup in Mesh
Admin → Auth → OAuth2 / OIDC:
issuerUrl(e.g.https://auth.example.com/application/o/mesh/)clientId,clientSecretscopes:openid profile email(default is fine)- Group mapping (optional):
adminGroupClaim: name of the group claim (e.g.groups)adminGroupValues: list of groups that receive the admin roleeditorGroupValues: editor groupsfallbackRole: what do users without a matching group get? Defaultviewer
Technical details
- PKCE + State + Nonce
- Token signed as HS256 JWT with MESH_SESSION_SECRET
- JWKS verification against the IdP
- Session TTL: 24 h
- Routes:
/api/auth/oidc/{start,callback,logout}
Bootstrap hard block
Without MESH_SESSION_SECRET ≥32 characters AND issuerUrl AND clientId, Mesh refuses to start in OAuth2 mode — instrumentation.ts performs the check at boot.
Troubleshooting OAuth2 / OIDC
| Symptom | Cause | Solution |
|---|---|---|
| "Redirect URI Error" at IdP | Callback URL not registered in IdP | Register https://<domain>/api/auth/oidc/callback as redirect URI in IdP (exact, HTTPS) |
| "Code exchange invalid" on callback page | Reverse proxy not setting X-Forwarded-Proto — app sends http:// instead of https:// to IdP |
Check proxy headers (see above) |
Redirect to 0.0.0.0:3000 after login |
Same cause — missing proxy headers on post-login redirect | Add proxy headers |
| "invalid_client" in container log | Client secret missing or wrong | Enter client secret in the auth form and save |
| "Invalid OIDC session" when saving | Browser has an old cookie (e.g. from open mode) |
Delete cookies for the Mesh domain, log in again |
| Server does not start in oauth2 mode | MESH_SESSION_SECRET, issuerUrl or clientId missing |
Check required fields — Mesh blocks start via hard check |
Tests
42+ Vitest tests for the auth layer. Run locally:
pnpm test --run