Skip to main content

Authentication

Multiforum supports two authentication modes:

  • local-dev provides the bootstrap-password sign-in flow used by the local Docker Compose quick-start. It is intended for evaluation and development.
  • auth0 uses Auth0 and @auth0/auth0-nuxt with 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:

  1. Auth0 identity — Auth0 authenticates the user and issues tokens.
  2. 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.
  3. 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 settingValue
Allowed Callback URLshttp://localhost:3000/auth/callback
Allowed Logout URLshttp://localhost:3000
Allowed Web Originshttp://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

  1. The user follows /auth/login, optionally with a relative returnTo query parameter.
  2. The route mounted by @auth0/auth0-nuxt redirects the user to Auth0.
  3. After authentication, Auth0 returns the authorization code to /auth/callback.
  4. The Nuxt server exchanges the code for tokens and creates a server-side session.
  5. On the next request, Nuxt reads the session and fetches the user's Multiforum profile from the GraphQL backend.
  6. 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 localStorage under the token key.
  • If localStorage is 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:

  1. server/middleware/2.auth-session.ts reads the Auth0 session in the Nitro request context.
  2. It obtains an API access token and resolves the user's Multiforum profile with the backend's getOwnEmail query.
  3. Stable profile fields are cached for one hour; the unread notification count is fetched on each request.
  4. plugins/auth-session.ts copies the profile into request-scoped Nuxt useState values.
  5. Nuxt serializes that state into the page payload and restores the same values in the browser during hydration.
  6. plugins/apollo-ssr-auth.ts authenticates 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 the frontend-data Docker 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.

VariableDescription
NUXT_AUTH0_DOMAINAuth0 tenant domain, such as your-tenant.us.auth0.com
NUXT_AUTH0_CLIENT_IDClient ID of the Auth0 Regular Web Application
NUXT_AUTH0_CLIENT_SECRETClient secret of the Auth0 Regular Web Application
NUXT_AUTH0_SESSION_SECRETLong random secret used to protect the Nuxt session
NUXT_AUTH0_APP_BASE_URLPublic base URL of the frontend, such as http://localhost:3000
NUXT_AUTH0_AUDIENCEAudience identifier of the Multiforum GraphQL API

Generate a session secret with:

openssl rand -hex 64

Serverless or multi-replica hosting also requires:

VariableDescription
UPSTASH_REDIS_REST_URLREST URL of the Upstash Redis session database
UPSTASH_REDIS_REST_TOKENAccess token for the Upstash Redis session database

Backend

VariableDescription
AUTH0_DOMAINAuth0 tenant domain used to locate the JWKS endpoint and user-info endpoint
AUTH0_CLIENT_IDAuth0 client ID used when identifying frontend-issued tokens

Backend token verification

The GraphQL backend verifies Auth0 JWT access tokens:

  1. It extracts the bearer token from the Authorization header.
  2. It loads Auth0's public signing key from https://$AUTH0_DOMAIN/.well-known/jwks.json.
  3. It verifies the token signature and claims.
  4. 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-auth cookie 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=true or PLAYWRIGHT_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_SECRET is set and stable across deployments.
  • In the production Compose stack, confirm the frontend-data volume 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_AUDIENCE exactly matches the audience configured for the GraphQL API.
  • Confirm offline access is enabled for that API.
  • Check /api/session/token and the GraphQL request's Authorization header.
  • 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.ts resolves the session before SSR.
  • Confirm components read authentication state through the useAuthState composables rather than module-level state or authentication-hint cookies.

Key frontend files

FilePurpose
nuxt.config.tsRegisters @auth0/auth0-nuxt, private Auth0 configuration, session storage, and auth-related cache rules
server/utils/session-store-factory.tsImplements the server-side Auth0 session store
server/middleware/2.auth-session.tsReads the session, obtains the API token, and resolves the Multiforum profile
plugins/auth-session.tsSeeds request-scoped authentication state for SSR and hydration
composables/useAuthState.tsExposes request-scoped authentication and profile state
plugins/apollo-ssr-auth.tsAuthenticates GraphQL requests during SSR
plugins/apollo-auth.client.tsSynchronizes the browser Apollo access token with the server session
components/auth/RequireAuth.vueRenders authentication- and ownership-aware UI
composables/useServerLogout.tsClears client state and starts server-side logout