Auch verfügbar in: 🇩🇪 Deutsch

Auth Modes — who can access what?

Inhalt

🇩🇪 Deutsch

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:

  1. Create at least one admin account (Admin → Users → "New User")
  2. Only then activate the mode
  3. 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; and proxy_set_header Host $http_host; in the location block.

Setup in your IdP

  1. Create an Application with:

    • Redirect URI: https://<mesh-url>/api/auth/oidc/callback
    • Scopes: openid + profile + email
    • Optional: enable group claim (for role mapping)
  2. 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, clientSecret
  • scopes: 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 role
    • editorGroupValues: editor groups
    • fallbackRole: what do users without a matching group get? Default viewer

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