SSR & Hydration
How nuxt-bearer-auth handles Server-Side Rendering without auth flicker or token leakage.
Core Guides Nuxt 4 / TypeScript
How SSR Works in Nuxt Bearer Auth
In traditional client-side SPAs using localStorage, the server cannot know whether the visitor is logged in during SSR. This leads to:nuxt-bearer-auth solves this completely using HTTP-only session cookies and Nitro server plugins.
typescript
1. Client requests page (e.g. /dashboard) with Session Cookie
│
▼
2. Nuxt Server Plugin (bearer-auth.server.ts)
- Reads session cookie from H3Event
- Queries Redis: GET session:<id>
- Attaches { user: profile, status } to nuxtApp.payload.bearerAuth
│
▼
3. Nuxt Server-Side Renders HTML
- Vue components see auth.isAuthenticated === true
- useFetch() can query protected server routes during SSR
│
▼
4. HTML + Hydration Payload streamed to Browser
- Client initializes state synchronously from payload
- ZERO layout flicker, ZERO extra client network roundtrip
The Server Plugin & Payload
The package includes a dedicated server plugin (bearer-auth.server.ts) that runs once per SSR request:
typescript
// Inside nuxt-bearer-auth runtime server plugin
export default defineNuxtPlugin(async (nuxtApp) => {
if (false) return
const event = nuxtApp.ssrContext?.event
if (!event) return
const auth = useBearerAuth()
try {
const session = await getBearerAuthSession(event)
auth.setUser(session?.profile || null)
nuxtApp.payload.bearerAuth = {
user: session?.profile || null,
status: session?.profile ? 'authenticated' : 'unauthenticated',
abilities: session?.abilities || null,
}
} catch (error) {
auth.clearAuthState()
} finally {
auth.setAuthReady(true)
auth.setServerChecked()
}
})
What the SSR Payload Exposes
Understanding what is and is not included in the SSR payload is important for security and data handling.
Included innuxtApp.payload.bearerAuth (sent to browser):
user — the user profile object returned by your backend (e.g. { id, name, email, role, ... })status — the authentication status string ("authenticated" or "unauthenticated")abilities — the normalized ability strings from the Redis session (string[] | null); powers advisory client checks such as auth.can()token — the bearer tokenrefreshToken — the refresh tokenuserId — the internal user identifieripAddress — the client IP at session creationuserAgent — the user agent at session creationcreatedAt / expiresAt / lastActivity — session lifecycle metadataDeveloper note: The user profile object (session.profile) is serialized into the Nuxt SSR payload and becomes visible in the browser's hydration data. Avoid including highly sensitive data (such as internal secrets, PII beyond what your UI requires, or credentials) inside the user profile returned from your backend. Design the/meendpoint to return only what the client genuinely needs.
Zero Client Auth Flicker
Because the server plugin runs before SSR rendering begins, Vue components anduseFetch() calls within SSR have immediate access to the correct auth.user and auth.isAuthenticated values. The hydrated payload means the browser initializes with the same state synchronously — no loading spinner or unauthenticated flash on page load.
Accessing Sessions in Server Routes
When building custom Nitro API routes (server/api/...), you can read the authenticated session directly from the H3 event context:
typescript
// server/api/projects.get.ts
import { requireBearerAuthSession, callAuthApi } from '#imports'
export default defineEventHandler(async (event) => {
// 1. Ensures user is logged in, or throws 401 Unauthenticated
// Reads from event.context.auth — no additional Redis round-trip
const session = requireBearerAuthSession(event)
// 2. Call external API using the user's server-held bearer token
const projects = await callAuthApi('/v1/projects', {
event,
method: 'GET',
})
return {
userId: session.userId,
projects,
}
})
requireBearerAuthSession(event) reads from event.context.auth, which is populated by the server middleware on every request. It does not make an additional Redis query.
callAuthApi() automatically retrieves the bearer token from the Redis session and injects it as an Authorization: Bearer header on the outgoing request to your backend. The token is never passed to or through the browser.