What Causes This Error?
This error occurs in H3 (Nuxt’s server framework) when a session has expired or is no longer valid. This typically happens with server-side session management.
Common Causes
- Session TTL (time-to-live) exceeded
- Session storage cleared
- Server restart (in-memory sessions)
- Invalid session ID
- Cookie expired before session
The Fix
Configure Session Properly
// server/api/auth/login.post.ts
export default defineEventHandler(async (event) => {
const session = await useSession(event, {
password: process.env.SESSION_SECRET!,
maxAge: 60 * 60 * 24 * 7, // 7 days
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax'
}
})
await session.update({
userId: 'user-123',
createdAt: Date.now()
})
return { success: true }
})
Handle Expired Sessions
// server/middleware/auth.ts
export default defineEventHandler(async (event) => {
try {
const session = await useSession(event, {
password: process.env.SESSION_SECRET!
})
// Check if session exists and is valid
if (!session.data.userId) {
// Session expired or invalid
return // Allow request to continue without auth
}
// Attach user to event context
event.context.auth = {
userId: session.data.userId
}
} catch (error) {
// Session error - clear it
console.warn('Session error:', error)
}
})
Refresh Session Before Expiry
// server/api/auth/refresh.post.ts
export default defineEventHandler(async (event) => {
const session = await useSession(event, {
password: process.env.SESSION_SECRET!,
maxAge: 60 * 60 * 24 * 7
})
if (!session.data.userId) {
throw createError({
statusCode: 401,
message: 'Session expired. Please login again.'
})
}
// Refresh session by updating
await session.update({
...session.data,
lastAccess: Date.now()
})
return { success: true }
})
Session Storage
In-Memory (Default - Not for Production)
// Sessions lost on server restart
const session = await useSession(event, {
password: process.env.SESSION_SECRET!
})
With Redis (Production)
// nuxt.config.ts
export default defineNuxtConfig({
nitro: {
storage: {
sessions: {
driver: 'redis',
url: process.env.REDIS_URL
}
}
}
})
// server/api/auth/session.ts
export default defineEventHandler(async (event) => {
const session = await useSession(event, {
password: process.env.SESSION_SECRET!,
name: 'session',
cookie: {
maxAge: 60 * 60 * 24 * 7
}
})
return { user: session.data }
})
Client-Side Handling
<script setup>
const { data, error, refresh } = await useFetch('/api/user')
watch(error, (err) => {
if (err?.statusCode === 401) {
// Session expired - redirect to login
navigateTo('/login?expired=true')
}
})
// Periodic session refresh
onMounted(() => {
const interval = setInterval(async () => {
try {
await $fetch('/api/auth/refresh', { method: 'POST' })
} catch (e) {
// Session expired
navigateTo('/login')
}
}, 15 * 60 * 1000) // Every 15 minutes
onUnmounted(() => clearInterval(interval))
})
</script>
API Middleware for Auth
// server/utils/requireAuth.ts
export async function requireAuth(event: H3Event) {
const session = await useSession(event, {
password: process.env.SESSION_SECRET!
})
if (!session.data.userId) {
throw createError({
statusCode: 401,
statusMessage: 'Session expired',
message: 'Please login to continue'
})
}
return session.data
}
// server/api/protected/data.ts
export default defineEventHandler(async (event) => {
const user = await requireAuth(event)
return {
message: `Hello ${user.userId}`
}
})
Session Configuration Options
await useSession(event, {
// Required - encrypt session data
password: process.env.SESSION_SECRET!, // Min 32 chars
// Session name (cookie name)
name: 'my-session',
// Session lifetime
maxAge: 60 * 60 * 24, // 24 hours in seconds
// Cookie options
cookie: {
httpOnly: true, // Not accessible via JS
secure: true, // HTTPS only
sameSite: 'lax', // CSRF protection
path: '/', // Cookie path
domain: '.example.com' // Cookie domain
}
})
Clearing Sessions
// server/api/auth/logout.post.ts
export default defineEventHandler(async (event) => {
const session = await useSession(event, {
password: process.env.SESSION_SECRET!
})
// Clear session data
await session.clear()
return { success: true }
})
Quick Checklist
- SESSION_SECRET is at least 32 characters
- maxAge is set appropriately
- Use persistent storage (Redis) in production
- Handle 401 errors on client side
- Implement session refresh mechanism
- Clear sessions properly on logout
- Cookie settings match your security requirements