Authentication overview
The dashboard UI is protected by Better Auth, mounted in-process at /api/auth/** by @onmax/nuxt-better-auth from apps/dashboard/server/auth.config.ts.
The boundary
Authentication follows the adapter rule at the package level:
packages/authholds the policy:authBetterAuthOptionsbuilds the database, policy, and provider options Better Auth needs. It has no HTTP server and no runtime imports, and its./configsubpath stays free of database dependencies so the login page can read feature flags without bundling one.packages/databaseowns the store: the Drizzle schema, the connection factory, and the checked-in migrations. It knows nothing about sign-in and must never importpackages/auth— see Database.apps/dashboard/server/auth.config.tscomposes that policy into the running instance. It is the only route in the app that resolves the connection string (throughpackages/database) and the signing secret, and therefore the only process that opens a connection to Postgres.
authBetterAuthOptions deliberately omits secret, baseURL, and trustedOrigins — the Nuxt module resolves those itself and constructs the actual instance, so the two cannot diverge. createAuth, which does build a full standalone instance, remains for callers that own their own secret and origin, such as the Better Auth CLI's schema-generation entry point.
Secrets
| Variable | Rule |
|---|---|
NUXT_BETTER_AUTH_SECRET | Required under this name in production; generate with openssl rand -base64 32 |
BETTER_AUTH_SECRET | Development-only fallback |
DATABASE_URL | Postgres connection string, resolved only by server/auth.config.ts (the pre-split AUTH_DATABASE_URL is still read when unset) |
Closed by default
Registration and GitHub OAuth are off until you turn them on, so a fresh deployment cannot be signed up for by a stranger:
AUTH_ENABLE_SIGNUP=trueenables self-registration at/signup(defaultfalse); with it off, that page shows a closed state and/loginhides the link to it;- GitHub OAuth requires both
GITHUB_CLIENT_IDandGITHUB_CLIENT_SECRET— see GitHub OAuth.
/login and /signup will keep advertising the old capabilities until rebuilt.SSR and sessions
The dashboard renders with SSR. The session cookie is scoped to the app's own origin, so the server resolves it directly from the incoming request before the first paint — a signed-out visitor never flashes protected content before redirecting.
Sessions are not control-plane authority
The Better Auth session protects the dashboard UI. The control-plane API (/rpc/**, /api/v1/**) uses an independent bearer-token scheme — see Protect endpoints.
Protect endpoints
The control plane fails closed. Reads (tasks.list, tasks.get, health) stay open for the dashboard; every mutation requires an operator-issued bearer credential, and without configuration every mutation is rejected.
GitHub OAuth
The login page offers a GitHub button only when both halves of the credential are set: