Skip to content
← Todas las preguntas
Avanzado

¿Cómo gestionas la autenticación en Nuxt 3?

NuxtArquitectura

El patrón estándar usa cuatro piezas que trabajan juntas: un composable useAuth que expone el state de autenticación y sus métodos, un plugin que inicializa la sesión de usuario al arrancar la app, un middleware de ruta que protege páginas, y un middleware de servidor que protege las rutas de la API. Los tokens se almacenan en cookies (no en localStorage) porque las cookies son accesibles durante SSR.

Por qué cookies y no localStorage

localStorage no existe en el servidor. Durante SSR, el servidor necesita saber quién es el usuario para renderizar contenido personalizado y proteger páginas. Las cookies se envían con cada petición HTTP, así que el servidor puede leerlas durante SSR y llamadas a la API.

ts
// composables/useAuth.ts
export function useAuth() {
  const user = useState<User | null>('auth-user', () => null)
  const token = useCookie('auth-token', {
    maxAge: 60 * 60 * 24 * 7,  // 7 días
    sameSite: 'lax',
    secure: true
  })

  const loggedIn = computed(() => !!user.value)

  async function login(email: string, password: string) {
    const response = await $fetch<{ user: User; token: string }>('/api/auth/login', {
      method: 'POST',
      body: { email, password }
    })
    token.value = response.token
    user.value = response.user
  }

  async function logout() {
    await $fetch('/api/auth/logout', { method: 'POST' })
    token.value = null
    user.value = null
    navigateTo('/login')
  }

  async function fetchUser() {
    if (!token.value) {
      user.value = null
      return
    }
    try {
      user.value = await $fetch<User>('/api/auth/me')
    } catch {
      token.value = null
      user.value = null
    }
  }

  return { user, token, loggedIn, login, logout, fetchUser }
}

useCookie es compatible con SSR: lee desde las cabeceras de la petición en el servidor y desde document.cookie en el cliente. useState garantiza que el state del usuario no se filtre entre peticiones en el servidor.

Plugin: inicializar la sesión al arrancar la app

ts
// plugins/auth.ts
export default defineNuxtPlugin(async () => {
  const { fetchUser } = useAuth()
  await fetchUser()
})

Se ejecuta una vez cuando la app arranca (tanto en servidor como en cliente). Si el usuario tiene una cookie de token válida, fetchUser carga su perfil. Si el token ha expirado o no es válido, limpia el state de autenticación.

Middleware de ruta: proteger páginas

ts
// middleware/auth.ts
export default defineNuxtRouteMiddleware(() => {
  const { loggedIn } = useAuth()

  if (!loggedIn.value) {
    return navigateTo('/login')
  }
})
ts
// middleware/guest.ts (redirige a usuarios autenticados fuera de la página de login)
export default defineNuxtRouteMiddleware(() => {
  const { loggedIn } = useAuth()

  if (loggedIn.value) {
    return navigateTo('/dashboard')
  }
})

Aplícalos a las páginas con definePageMeta:

vue
<!-- pages/dashboard.vue -->
<script setup>
definePageMeta({ middleware: 'auth' })
</script>
vue
<!-- pages/login.vue -->
<script setup>
definePageMeta({ middleware: 'guest' })
</script>

Middleware de servidor: proteger rutas de la API

El middleware de ruta solo protege páginas. Cualquiera puede llamar directamente a /api/admin/users. Protege los datos en la capa de servidor:

ts
// server/middleware/auth.ts
export default defineEventHandler((event) => {
  const url = getRequestURL(event).pathname

  if (!url.startsWith('/api/') || url.startsWith('/api/auth/')) {
    return  // omitir rutas no-API y endpoints de auth públicos
  }

  const token = getCookie(event, 'auth-token')
  if (!token) {
    throw createError({ statusCode: 401, statusMessage: 'No autorizado' })
  }

  try {
    event.context.user = verifyJWT(token)
  } catch {
    throw createError({ statusCode: 401, statusMessage: 'Token inválido' })
  }
})

Ahora cada ruta de la API puede acceder al usuario autenticado a través de event.context.user:

ts
// server/api/auth/me.get.ts
export default defineEventHandler((event) => {
  const user = event.context.user
  if (!user) throw createError({ statusCode: 401 })
  return user
})

Rutas de la API de servidor: login y logout

ts
// server/api/auth/login.post.ts
export default defineEventHandler(async (event) => {
  const { email, password } = await readBody(event)

  const user = await findUserByEmail(email)
  if (!user || !await verifyPassword(password, user.passwordHash)) {
    throw createError({ statusCode: 401, statusMessage: 'Credenciales inválidas' })
  }

  const token = signJWT({ userId: user.id, role: user.role })

  setCookie(event, 'auth-token', token, {
    httpOnly: true,
    secure: true,
    sameSite: 'lax',
    maxAge: 60 * 60 * 24 * 7
  })

  return { user: { id: user.id, name: user.name, email: user.email, role: user.role } }
})
ts
// server/api/auth/logout.post.ts
export default defineEventHandler((event) => {
  deleteCookie(event, 'auth-token')
  return { ok: true }
})

Configurar httpOnly: true en la cookie del lado del servidor impide que JavaScript acceda al token, protegiéndolo contra ataques XSS. El useCookie('auth-token') del lado del cliente puede detectar si la cookie existe (para la comprobación loggedIn) pero no puede leer el valor de una cookie httpOnly. Para el valor del token en sí, el servidor lo gestiona todo.

Cómo encajan las piezas

La app arranca
  → plugin auth se ejecuta → fetchUser() → lee la cookie → carga el usuario

Usuario visita /dashboard
  → middleware auth → ¿loggedIn? → sí → renderizar página
                                 → no → redirigir a /login

Usuario llama a /api/admin/users
  → middleware del servidor → ¿token válido? → sí → adjuntar usuario al contexto → gestionar petición
                                              → no → 401 No autorizado

Usuario hace clic en logout
  → useAuth().logout() → POST /api/auth/logout → borrar cookie → borrar state → redirigir

Resumen

PiezaUbicaciónResponsabilidad
Composable useAuthcomposables/State de auth, métodos login/logout
Plugin de authplugins/Inicializar sesión al arrancar la app
Middleware de rutamiddleware/Proteger páginas, redirigir usuarios no autenticados
Middleware de servidorserver/middleware/Proteger rutas de la API, validar tokens
Rutas de la API de servidorserver/api/auth/Login, logout, gestión de tokens
CookieEnviada con cada peticiónAlmacenamiento del token (compatible con SSR, httpOnly)

Publicado bajo la licencia MIT.