Auth.js v5 (antes NextAuth.js) es la librería de autenticación oficial recomendada para Next.js 15, y ofrece un único helper auth() universal que funciona en Server Components, Route Handlers, Server Actions y middleware sin configuración adicional. La versión 5, estable desde finales de 2024 y consolidada durante 2025-2026, reemplaza el viejo getServerSession() por una API unificada compatible con el App Router, sesiones JWT o de base de datos, más de 80 proveedores OAuth y adaptadores para Drizzle, Prisma y otros ORMs. Honestamente, después de migrar varios proyectos desde v4, la diferencia operativa es enorme. Aquí cubro instalación, OAuth, credenciales, sesiones, callbacks y patrones de seguridad listos para producción.
Auth.js v5 unifica la API de autenticación en una sola función auth() que reemplaza a getServerSession, getSession, withAuth y getToken del v4.
La configuración vive en un archivo raíz auth.ts que exporta handlers, signIn, signOut y auth, eliminando el viejo patrón de [...nextauth]/route.ts con opciones inline.
El proveedor Credentials soporta validación con Zod y devuelve un objeto user que se persiste en la sesión JWT por defecto; la sesión de base de datos requiere un adapter compatible.
El middleware con auth ejecuta en el Edge Runtime y por eso no puede importar adapters de Node.js; la solución recomendada es el patrón "split config" con auth.config.ts.
Las callbacks jwt y session son el lugar correcto para inyectar roles, claims personalizados y datos de perfil sin sobrecargar el cliente.
Auth.js v5 incluye protección CSRF automática, cookies __Host- con SameSite=Lax y rotación de tokens; aún así debes establecer AUTH_SECRET y trustHost correctamente en producción.
¿Qué es Auth.js v5 y en qué se diferencia de NextAuth v4?
Auth.js v5 es la evolución de NextAuth.js, renombrada para reflejar que la librería ya no está atada a Next.js: el mismo núcleo funciona ahora con SvelteKit, Express, SolidStart y Qwik a través de paquetes específicos como @auth/sveltekit. Para Next.js seguimos instalando next-auth, pero el paquete expone una superficie de API completamente nueva. He migrado tres aplicaciones en producción desde v4 y la diferencia más notable es que todo pasa por un único helper auth(): el mismo símbolo funciona como middleware, como utilidad para Server Components, como wrapper de Route Handlers y como lector de sesión dentro de Server Actions.
En v4 teníamos una colección de funciones especializadas: getServerSession para Pages Router, getToken para inspeccionar el JWT, withAuth para envolver middleware y useSession en el cliente. Cada una con su propia firma y comportamiento sutil. La v5 colapsa toda esa familia en auth() y mantiene useSession solo donde realmente tiene sentido (Client Components que necesitan reactividad). El segundo cambio importante es que la configuración se exporta desde un archivo raíz en lugar de vivir embebida en el handler de la API; esto permite que el middleware y el handler compartan exactamente las mismas opciones sin duplicación, y habilita el "split config" necesario para que el Edge Runtime no intente cargar dependencias de Node.
Cómo instalar Auth.js v5 en Next.js 15 paso a paso
La instalación tiene tres archivos mínimos: auth.ts en la raíz con la configuración, middleware.ts que re-exporta el helper, y app/api/auth/[...nextauth]/route.ts que expone los handlers HTTP. Empieza instalando la librería y generando un secreto criptográficamente fuerte:
pnpm add next-auth@beta
# Genera AUTH_SECRET (32 bytes en base64)
npx auth secret
El comando npx auth secret escribe automáticamente AUTH_SECRET en tu .env.local. Este secreto firma los JWT y las cookies de sesión; rotarlo invalida todas las sesiones activas. A continuación crea auth.ts en la raíz del proyecto (al mismo nivel que next.config.ts):
// auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
import Google from "next-auth/providers/google"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub, Google],
session: { strategy: "jwt" },
pages: { signIn: "/login" },
})
El handler HTTP es un re-export de una línea. Crea app/api/auth/[...nextauth]/route.ts:
// app/api/auth/[...nextauth]/route.ts
export { GET, POST } from "@/auth"
Para leer la sesión desde cualquier Server Component, importa auth directamente. No hay SessionProvider obligatorio en el servidor, y la lectura es estática por request (deduplicada con cache() internamente):
// app/dashboard/page.tsx
import { auth } from "@/auth"
import { redirect } from "next/navigation"
export default async function Dashboard() {
const session = await auth()
if (!session?.user) redirect("/login")
return <h1>Hola {session.user.name}</h1>
}
Configurar OAuth con Google, GitHub y otros proveedores
Auth.js v5 soporta más de 80 proveedores OAuth y OIDC con configuración mínima. Para los proveedores estándar basta con declarar el provider (la librería autodetecta AUTH_GITHUB_ID y AUTH_GITHUB_SECRET en variables de entorno usando el nombre del provider en mayúsculas). Esto es un cambio respecto a v4, donde había que pasar explícitamente clientId y clientSecret. En GitHub crea una OAuth App apuntando a https://tu-dominio.com/api/auth/callback/github como URL de callback. En Google Cloud, configura un OAuth 2.0 Client ID con el mismo patrón sustituyendo github por google.
Para iniciar el flujo OAuth desde un Server Component o Server Action, llama a signIn("github"). Auth.js gestiona los redirects, el state parameter y la PKCE automáticamente. Si necesitas scopes adicionales (por ejemplo, leer repositorios privados de GitHub), pasa un objeto al provider:
El proveedor Credentials sigue existiendo en v5 para login con email/contraseña, aunque la documentación oficial recomienda OAuth siempre que sea posible. La función authorize recibe las credenciales del formulario y debe devolver el objeto user o null. La forma robusta es validar con Zod antes de tocar la base de datos:
// auth.ts
import Credentials from "next-auth/providers/credentials"
import { z } from "zod"
import bcrypt from "bcryptjs"
import { getUserByEmail } from "@/lib/users"
const credentialsSchema = z.object({
email: z.string().email(),
password: z.string().min(8).max(72),
})
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [
Credentials({
async authorize(raw) {
const parsed = credentialsSchema.safeParse(raw)
if (!parsed.success) return null
const { email, password } = parsed.data
const user = await getUserByEmail(email)
if (!user?.passwordHash) return null
const ok = await bcrypt.compare(password, user.passwordHash)
if (!ok) return null
return { id: user.id, email: user.email, name: user.name }
},
}),
],
})
Devolver null en lugar de lanzar excepciones es deliberado: evita filtrar si el email existe o no, mitigando enumeración de usuarios. Limita el campo password a 72 bytes porque bcrypt trunca silenciosamente cualquier entrada más larga, lo que puede crear credenciales que parecen válidas pero no lo son tras una rotación de hash. Para emparejar esto con un patrón completo de manejo de formularios y errores, revisa la guía sobre Server Actions con validación y seguridad.
Proteger rutas con middleware en el Edge Runtime
El middleware de Next.js 15 corre por defecto en el Edge Runtime, donde APIs de Node como bcrypt, drivers de bases de datos o fs no están disponibles. Si tu auth.ts importa un adapter de Prisma o llama a bcrypt dentro de authorize, importar ese archivo desde el middleware romperá el build. La solución oficial es el patrón "split config": separa la configuración mínima compatible con el Edge en auth.config.ts y compón ambos archivos:
// auth.config.ts (Edge-safe, sin adapter ni bcrypt)
import GitHub from "next-auth/providers/github"
import type { NextAuthConfig } from "next-auth"
export default {
providers: [GitHub],
callbacks: {
authorized({ auth, request: { nextUrl } }) {
const isLoggedIn = !!auth?.user
const onDashboard = nextUrl.pathname.startsWith("/dashboard")
if (onDashboard) return isLoggedIn
return true
},
},
} satisfies NextAuthConfig
El callback authorized decide qué requests dejan pasar. Devolver false redirige automáticamente a la página de login configurada en pages.signIn. Para patrones más sofisticados como rate limiting, geofencing o RBAC granular, complementa esto con el middleware avanzado para protección de rutas que cubre los matchers y la composición en profundidad.
Sesiones JWT vs sesiones de base de datos
Auth.js soporta dos estrategias de sesión y la elección impacta rendimiento, seguridad y operabilidad. Por defecto, sin adapter, la sesión vive en un JWT firmado almacenado en una cookie __Secure-authjs.session-token. Con un adapter como @auth/drizzle-adapter o @auth/prisma-adapter configurado y session.strategy: "database", la cookie solo contiene un identificador opaco y la sesión completa vive en la tabla sessions.
Característica
JWT
Base de datos
Latencia por request
Cero (sin I/O)
~1 query SQL
Revocación instantánea
Difícil (hasta expirar)
Inmediata (borrar fila)
Tamaño de cookie
1-4 KB
~100 bytes
Compatible con Edge
Sí
Solo con driver Edge
Rotación de claves
Requiere KMS
Trivial
Reto operativo
Limitar claims
Limpieza de expiradas
Para SaaS multitenant con miles de usuarios concurrentes, JWT escala mejor porque elimina la query de validación. Para aplicaciones donde necesites cerrar sesión remotamente (banca, panel de admin, herramientas internas), la estrategia de base de datos es prácticamente obligatoria. La guía oficial de migración de Auth.js documenta las diferencias en los callbacks: jwt solo se ejecuta con JWT, mientras que session corre en ambas estrategias.
Callbacks: añadir roles y claims personalizados
Las callbacks son el punto de extensión más útil de Auth.js. La callback jwt se ejecuta al crear o actualizar el token; la callback session formatea lo que recibe el cliente. El patrón canónico para inyectar un rol RBAC desde la base de datos es:
// auth.ts (fragmento)
callbacks: {
async jwt({ token, user }) {
if (user) {
// user solo existe en el primer login
const dbUser = await getUserById(user.id!)
token.role = dbUser?.role ?? "user"
token.orgId = dbUser?.orgId
}
return token
},
async session({ session, token }) {
session.user.role = token.role as "admin" | "user"
session.user.orgId = token.orgId as string
return session
},
}
Extiende los tipos en next-auth.d.ts para que TypeScript reconozca session.user.role:
Una de las mejoras grandes de v5 es que signIn y signOut son funciones server-side invocables desde Server Actions, sin necesidad de exponer endpoints adicionales ni de usar useSession con sus consiguientes redibujos. Un formulario de login típico se ve así:
El detalle crítico es redirect: false: sin él, Auth.js lanza un redirect interno que tu try/catch capturaría, rompiendo el flujo. Después del éxito invocas redirect("/dashboard") fuera del bloque try para que la excepción especial de redirect de Next.js se propague. En el Client Component, conecta el form con useActionState:
Lista de verificación de seguridad para producción
Auth.js es seguro por defecto, pero hay decisiones de configuración que dependen de tu entorno. He auditado decenas de despliegues y estos son los puntos que más se descuidan:
AUTH_SECRET: 32+ bytes aleatorios, único por entorno (dev/staging/prod). Nunca commitear; rotar trimestralmente.
Cookies: en producción Auth.js usa el prefijo __Secure- y SameSite=Lax. Si tu app está embebida en iframes de terceros, considera SameSite=None con prefijo __Host-, sabiendo que sacrifica protección CSRF cross-site.
trustHost: solo true detrás de un proxy reverso confiable. En Vercel ya está activado; en Docker self-hosted detrás de Traefik o NGINX configúralo explícitamente.
Rate limiting sobre /api/auth/callback/credentials: el provider Credentials no lo trae integrado. Añade Upstash Rate Limit o un middleware con @vercel/kv antes del endpoint.
Account linking: allowDangerousEmailAccountLinking: false por defecto previene secuestro de cuentas vía OAuth con email no verificado. No lo actives sin entender el riesgo.
Logs PII: configura logger.error para redactar emails antes de enviarlos a Sentry o Datadog.
CSRF: protegido automáticamente vía double-submit cookie. No deshabilitar.
Para una referencia adicional, consulta la documentación oficial de autenticación en Next.js y revisa periódicamente las notas de release de Auth.js en GitHub donde se publican los advisories de seguridad. La combinación de Auth.js bien configurado con middleware de protección de rutas y Server Actions tipadas cubre el 95% de los requisitos de autenticación moderna en Next.js 15.
Preguntas frecuentes
¿Auth.js v5 es lo mismo que NextAuth.js?
Sí, son el mismo proyecto. Auth.js es la marca paraguas que abarca SvelteKit, Express y otros frameworks; para Next.js se sigue instalando como next-auth en npm, ahora en su versión 5. La v4 sigue mantenida con parches de seguridad pero no recibe nuevas features.
¿Cómo migro de getServerSession a auth() en v5?
Sustituye getServerSession(authOptions) por await auth(). Las authOptions se mueven al archivo auth.ts raíz y se pasan a NextAuth(). El objeto sesión devuelto tiene la misma forma, por lo que el código que lee session.user no necesita cambios.
¿Puedo usar Auth.js v5 con Prisma o Drizzle?
Sí. Instala @auth/prisma-adapter o @auth/drizzle-adapter y pásalo en la opción adapter. Recuerda mover el adapter fuera de auth.config.ts si usas el patrón "split config" para middleware, porque el Edge Runtime no soporta drivers de base de datos basados en Node.
¿Por qué useSession no se actualiza tras cambiar el perfil?
Porque el JWT se cachea hasta su próxima rotación. Llama a update(newData) devuelto por useSession para forzar una nueva ejecución de la callback jwt con el flag trigger: "update", donde puedes leer los nuevos datos y reescribir el token.
¿Auth.js v5 funciona con el Edge Runtime?
El core es compatible con Edge, pero los adapters de base de datos basados en drivers de Node no lo son. Usa el patrón "split config" con auth.config.ts (sin adapter, providers Edge-safe) para el middleware, y la configuración completa con adapter para los Route Handlers y Server Components que corren en Node.
¿Es seguro almacenar el rol del usuario en el JWT?
Sí, siempre que verifiques la firma (algo que Auth.js hace automáticamente) y mantengas AUTH_SECRET a salvo. Para cambios de rol que deban surtir efecto inmediato, llama a update() desde el cliente o cambia a sesiones de base de datos donde puedes revocar reescribiendo la fila.
Guía práctica sobre las cinco estrategias de renderizado en Next.js 15 con App Router: SSG, ISR, SSR, PPR y CSR. Con código funcional, criterios de decisión y lo que cambia en Next.js 16 con Cache Components.
Aprende a construir APIs con Route Handlers en Next.js 15. Guía práctica con ejemplos de CRUD, CORS, streaming, webhooks, subida de archivos y patrones BFF. Actualizada para Next.js 15 y 16.
Domina React Server Components en Next.js 15: obtención de datos con async/await, streaming progresivo con Suspense, estrategias de caché y revalidación, y la directiva use cache. Guía práctica con patrones reales.