Authentication
Multiforum supports two authentication modes:
local-devprovides the bootstrap-password sign-in flow used by the local Docker Compose quick-start. It is intended for evaluation and development.auth0uses Auth0 and@auth0/auth0-nuxtwith a server-session model. Use it for production deployments.
In Auth0 mode, Auth0 handles identity while Multiforum's Nuxt server owns the application session and resolves the user's Multiforum profile from the GraphQL backend.
This makes server-rendered pages authentication-aware: navigation, owner/moderator controls, and other personalized UI can be correct on the first render without relying on client-side authentication hints.
Architecture overview
Production Auth0 authentication has three related pieces:
- Auth0 identity — Auth0 authenticates the user and issues tokens.
- Nuxt server session — the browser receives a secure session cookie that contains only a session identifier. The session and tokens are stored on the server.
- Multiforum profile — the Nuxt server uses the API access token to fetch the application's username, moderation profile name, avatar, and notification count from the GraphQL backend.
The older auth-hint and username-hint cookie system is no longer used.
Auth0 application configuration
Configure the frontend as a Regular Web Application (a confidential client) in the Auth0 dashboard, not as a Single Page Application.
For local development at http://localhost:3000, configure:
| Auth0 setting | Value |
|---|---|
| Allowed Callback URLs | http://localhost:3000/auth/callback |
| Allowed Logout URLs | http://localhost:3000 |
| Allowed Web Origins | http://localhost:3000 |
Add the equivalent HTTPS URLs for every deployed environment. The callback URL
must end in /auth/callback.
The Auth0 API used by the GraphQL backend must have Allow Offline Access enabled so the server session can obtain a refresh token.
Authentication flow
Login
- The user follows
/auth/login, optionally with a relativereturnToquery parameter. - The route mounted by
@auth0/auth0-nuxtredirects the user to Auth0. - After authentication, Auth0 returns the authorization code to
/auth/callback. - The Nuxt server exchanges the code for tokens and creates a server-side session.
- On the next request, Nuxt reads the session and fetches the user's Multiforum profile from the GraphQL backend.
- The resolved authentication state is included in the Nuxt payload so the server render and client hydration start with the same values.
The browser does not run the Auth0 SPA SDK or exchange the authorization code itself.
Session management
@auth0/auth0-nuxt reads the session and refreshes the API access token on the
server when necessary. Multiforum does not poll Auth0 from the browser every 60
seconds.
The refresh token and complete Auth0 session remain in the server-side session store. A small, secure session-ID cookie associates the browser with that session.
For authenticated GraphQL requests:
- During SSR, the server supplies the access token directly to Apollo from the current request context.
- In the browser, a same-origin session endpoint supplies an access token and
Apollo currently stores it in
localStorageunder thetokenkey. - If
localStorageis unavailable, Apollo can use the session-backed token in memory instead.
The access token should therefore still be treated as browser-accessible. The refresh token is not exposed to the browser.
Logout
Multiforum first clears its client-side Apollo and profile state, then redirects
to /auth/logout. The route mounted by @auth0/auth0-nuxt clears the Nuxt
session and logs the user out of the Auth0 SSO session.
The SDK also mounts /auth/backchannel-logout. Back-channel invalidation is
currently best-effort because the custom key/value session store does not yet
index sessions by Auth0 subject or session ID.
Server-side rendering
Authentication state flows through an SSR request as follows:
server/middleware/2.auth-session.tsreads the Auth0 session in the Nitro request context.- It obtains an API access token and resolves the user's Multiforum profile
with the backend's
getOwnEmailquery. - Stable profile fields are cached for one hour; the unread notification count is fetched on each request.
plugins/auth-session.tscopies the profile into request-scoped NuxtuseStatevalues.- Nuxt serializes that state into the page payload and restores the same values in the browser during hydration.
plugins/apollo-ssr-auth.tsauthenticates GraphQL queries made while rendering the page on the server.
Authenticated SSR pages must not use a shared response cache. Otherwise one user's personalized HTML could be served to another user.
Session storage
The default stateless @auth0/auth0-nuxt session stores the complete encrypted
token set in a cookie. Once a refresh token is included, that cookie can exceed
the browser's size limit. Multiforum therefore uses a custom server-side session
store and keeps only a small session identifier in the cookie.
- Nuxt development: filesystem-backed storage in
.auth0-sessions - Official single-host container: filesystem-backed storage under
/app/data, persisted by thefrontend-dataDocker volume - Serverless or multi-replica hosting: shared Upstash Redis storage with a 30-day session-entry TTL
The production Compose stack does not require Redis when it runs one frontend replica. A persistent shared store is required for serverless or multi-replica deployments so every instance can access the same sessions.
Required environment variables
Frontend
These variables configure the private runtimeConfig.auth0 block in Nuxt. Do
not expose the client secret or session secret through runtimeConfig.public or
VITE_* variables.
| Variable | Description |
|---|---|
NUXT_AUTH0_DOMAIN | Auth0 tenant domain, such as your-tenant.us.auth0.com |
NUXT_AUTH0_CLIENT_ID | Client ID of the Auth0 Regular Web Application |
NUXT_AUTH0_CLIENT_SECRET | Client secret of the Auth0 Regular Web Application |
NUXT_AUTH0_SESSION_SECRET | Long random secret used to protect the Nuxt session |
NUXT_AUTH0_APP_BASE_URL | Public base URL of the frontend, such as http://localhost:3000 |
NUXT_AUTH0_AUDIENCE | Audience identifier of the Multiforum GraphQL API |
Generate a session secret with:
openssl rand -hex 64
Serverless or multi-replica hosting also requires:
| Variable | Description |
|---|---|
UPSTASH_REDIS_REST_URL | REST URL of the Upstash Redis session database |
UPSTASH_REDIS_REST_TOKEN | Access token for the Upstash Redis session database |
Backend
| Variable | Description |
|---|---|
AUTH0_DOMAIN | Auth0 tenant domain used to locate the JWKS endpoint and user-info endpoint |
AUTH0_CLIENT_ID | Auth0 client ID used when identifying frontend-issued tokens |
Backend token verification
The GraphQL backend verifies Auth0 JWT access tokens:
- It extracts the bearer token from the
Authorizationheader. - It loads Auth0's public signing key from
https://$AUTH0_DOMAIN/.well-known/jwks.json. - It verifies the token signature and claims.
- It resolves the associated user information and caches it for 15 minutes.
Testing and mock authentication
Mock authentication is limited to test environments.
- The frontend must be built or started with
VITE_E2E_MOCK_MODE=true. - Playwright supplies a test-only
mock-authcookie containing the seeded user profile. The Nuxt middleware accepts it only while mock mode is enabled. - The backend accepts mock tokens when
E2E_MOCK_AUTH=trueorPLAYWRIGHT_MOCK_AUTH=true.
Never enable these flags in production.
Troubleshooting
Login or callback fails
- Confirm the Auth0 application is a Regular Web Application.
- Confirm the callback URL exactly matches
$NUXT_AUTH0_APP_BASE_URL/auth/callback. - Confirm the client ID, client secret, domain, and application base URL belong to the same Auth0 application.
Session disappears after login
- Confirm
NUXT_AUTH0_SESSION_SECRETis set and stable across deployments. - In the production Compose stack, confirm the
frontend-datavolume is mounted and was not removed during an upgrade. - On serverless or multi-replica hosting, verify the Upstash URL and token and ensure every function instance uses the same database.
- Confirm cookies are being sent over HTTPS in production.
The user is logged in but API requests are unauthenticated
- Confirm
NUXT_AUTH0_AUDIENCEexactly matches the audience configured for the GraphQL API. - Confirm offline access is enabled for that API.
- Check
/api/session/tokenand the GraphQL request'sAuthorizationheader. - Look for Auth0 session or token-refresh errors in the server logs.
Authentication UI differs between server and client
- Confirm authenticated routes are rendered per request and are not stored in a shared route cache.
- Confirm
server/middleware/2.auth-session.tsresolves the session before SSR. - Confirm components read authentication state through the
useAuthStatecomposables rather than module-level state or authentication-hint cookies.
Key frontend files
| File | Purpose |
|---|---|
nuxt.config.ts | Registers @auth0/auth0-nuxt, private Auth0 configuration, session storage, and auth-related cache rules |
server/utils/session-store-factory.ts | Implements the server-side Auth0 session store |
server/middleware/2.auth-session.ts | Reads the session, obtains the API token, and resolves the Multiforum profile |
plugins/auth-session.ts | Seeds request-scoped authentication state for SSR and hydration |
composables/useAuthState.ts | Exposes request-scoped authentication and profile state |
plugins/apollo-ssr-auth.ts | Authenticates GraphQL requests during SSR |
plugins/apollo-auth.client.ts | Synchronizes the browser Apollo access token with the server session |
components/auth/RequireAuth.vue | Renders authentication- and ownership-aware UI |
composables/useServerLogout.ts | Clears client state and starts server-side logout |