¿JWT o Session? Deja de dudar: después de leer esto lo entenderás

El cursor parpadea entre session: { strategy: "jwt" } y session: { strategy: "database" }. El proyecto va a salir y sigues dudando entre JWT y Session.
En la elección técnica, ambas opciones parecen buenas, pero cuesta saber cuál encaja en tu proyecto. JWT es stateless y no consulta la base de datos, pero no se revoca al instante; Session es stateful y permite control inmediato, pero cada petición consulta la base de datos. En este artículo repasamos las diferencias y cómo elegir.
Primero, aclaremos qué son
Mucha gente recita la definición de JWT y Session, pero explicarlas bien cuesta. Me gusta usar una analogía:
Session es como la tarjeta de un gimnasio. Al apuntarte, el gimnasio guarda tu información en su sistema. Cada vez que entrenas, pasas la tarjeta (un Session ID) y recepción consulta quién eres, cuándo vence la membresía y cuántas clases te quedan. Todo lo importante está en la base de datos del gimnasio.
JWT es como un documento de identidad. Tu información (nombre, fecha de nacimiento, dirección…) va impresa en el documento. Cuando hay que acreditar identidad, lo muestras y quien lo ve ya sabe quién eres, sin consultar ningún sistema.
Así queda claro cómo funcionan:
-
Session: tras el login, el servidor genera un Session ID, guarda la información del usuario en la base de datos y envía el Session ID al navegador por cookie. En cada petición, el navegador lo envía y el servidor busca al usuario correspondiente.
-
JWT: tras el login, el servidor empaqueta la información del usuario en un token cifrado (JWT) y lo envía al navegador. En cada petición, el navegador lo envía y el servidor solo valida que el token sea válido, sin consultar la base de datos.
JWT en profundidad
¿Por qué JWT es tan popular?
JWT tiene mucha tracción entre desarrolladores, sobre todo en equipos Serverless y microservicios. La razón es simple: no tienes que preocuparte por la base de datos.
Un amigo montó una tienda online en Vercel y eligió JWT. Me dijo que lo mejor es lo sencillo: no te preocupas por conexiones a la base de datos ni por el rendimiento de la tabla Session, y desplegar en edge no es problema. El usuario inicia sesión una vez, recibe el JWT y en adelante todas las peticiones lo llevan; el servidor valida la firma y listo.
JWT encaja especialmente en estos escenarios:
1. Serverless / edge computing
Si despliegas en Vercel, Cloudflare Workers u otras plataformas similares, JWT es casi estándar. Las funciones son stateless y cada petición puede ir a otro servidor. Con Session necesitas Redis u otro almacén compartido; con JWT el token lleva la información y da igual qué servidor procese la petición.
2. Alta concurrencia
Quien ha hecho flash sales sabe que las consultas a base de datos son un cuello de botella. Con Session, cada petición consulta la base de datos; con JWT solo consultas al login y el resto es validación local, mucho más rápida.
3. Arquitectura de microservicios
Con varios servicios (usuarios, pedidos, pagos…), JWT permite compartir autenticación entre ellos sin que cada uno consulte la Session del usuario ni un almacén central de sesiones.
Las trampas de JWT
Suena bien, pero JWT también tiene problemas, y algunos son profundos.
La trampa más grande: no revocar al instante
Una vez detectamos que una cuenta estaba comprometida y quisimos cerrar la sesión de inmediato. Con Session, borras el registro en la base de datos y listo. Con JWT, incómodo: una vez emitido, sigue válido hasta que expire.
¿Lista negra? Sí, pero pierdes la ventaja stateless. Cada petición consulta la lista negra, igual que consultar una tabla Session.
Por eso con JWT suele ponerse expiración corta, por ejemplo 15 minutos, más refresh token. Si roban el token, como mucho dura 15 minutos.
Límite de tamaño de cookie
JWT codifica la información del usuario en el token. Si metes demasiado (roles, permisos, preferencias…), el token crece. Las cookies del navegador tienen un límite de 4 KB; si lo superas, no cabe.
Lección: no metas todo en el JWT. Solo lo básico, como ID de usuario y expiración. El resto, consúltalo cuando haga falta.
Configuración JWT con NextAuth.js en la práctica
Si usas Next.js, NextAuth.js (ahora Auth.js) es la opción de autenticación más extendida. Configurar JWT es sencillo:
// auth.ts
import NextAuth from "next-auth"
import GitHub from "next-auth/providers/github"
export const { handlers, signIn, signOut, auth } = NextAuth({
providers: [GitHub],
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60, // 30 días
},
callbacks: {
async jwt({ token, user }) {
// En el primer login, añade la info de user al token
if (user) {
token.id = user.id
token.role = user.role
}
return token
},
async session({ session, token }) {
// Expone la info del token al cliente
if (token) {
session.user.id = token.id
session.user.role = token.role
}
return session
},
},
})
Algunos puntos a tener en cuenta:
-
Configura
NEXTAUTH_SECRET(en Auth.js v5 se llamaAUTH_SECRET). Genera una clave segura connpx auth secret. Sirve para firmar y cifrar el JWT; no la compartas. -
Sesión deslizante: si quieres renovar automáticamente mientras el usuario está activo, configura
updateAge:
session: {
strategy: "jwt",
maxAge: 30 * 24 * 60 * 60,
updateAge: 24 * 60 * 60, // Renovar cada 24 horas
}
Así, si hay actividad en 24 horas, la sesión se renueva y no hay cierre súbito.
- Novedades de 2025: si despliegas en Edge Runtime (por ejemplo middleware), conviene dividir la configuración:
// auth.config.ts - puede ejecutarse en Edge
export default {
providers: [GitHub],
session: { strategy: "jwt" },
}
// auth.ts - incluye operaciones de BD, solo en Node.js
import { PrismaAdapter } from "@auth/prisma-adapter"
import authConfig from "./auth.config"
export const { handlers, auth } = NextAuth({
...authConfig,
adapter: PrismaAdapter(prisma),
})
Edge Runtime no soporta algunas APIs de Node.js; tras dividir, el middleware usa auth.config.ts y las rutas de servidor usan auth.ts completo.
Database Session en profundidad
¿Cuándo Session es obligatorio?
Aunque JWT es cómodo, hay escenarios donde toca usar Session.
Hice un panel de administración para una empresa financiera. Su requisito era claro: ante un login anómalo, hay que expulsar al usuario al instante. Ahí JWT no encaja.
Database Session encaja en estos casos:
1. Apps que necesitan control inmediato
Límite de dispositivos (solo uno activo), cierre forzado (admin expulsa usuarios), invalidar todas las sesiones al cambiar la contraseña… Con Session es directo; con JWT hace falta mucha lógica extra.
2. Escenarios de alta seguridad
Banca, salud, administración pública: cada petición debe reflejar el permiso más reciente. Session consulta la base de datos en cada petición y los cambios de permiso aplican al momento.
3. Apps monolíticas tradicionales
Si tu app es un monolito con servidor y base de datos juntos, Session no penaliza mucho. La latencia de consulta es baja y controlas mejor la sesión.
Problemas de rendimiento de Session
El mayor problema: cada petición consulta la base de datos. Con mucho tráfico, puede ser un cuello de botella.
Algunas optimizaciones:
-
Guardar Session en Redis: mucho más rápido que una base de datos tradicional y con expiración automática.
-
Minimizar datos de Session: solo el ID de usuario; el resto, cuando haga falta.
-
Expiración razonable: limpia Sessions vencidas o la tabla crece sin control.
Configuración Session en Next.js en la práctica
NextAuth.js usa JWT por defecto. Para Database Session necesitas un adapter:
// auth.ts
import NextAuth from "next-auth"
import { PrismaAdapter } from "@auth/prisma-adapter"
import { PrismaClient } from "@prisma/client"
import GitHub from "next-auth/providers/github"
const prisma = new PrismaClient()
export const { handlers, auth } = NextAuth({
adapter: PrismaAdapter(prisma),
providers: [GitHub],
session: {
strategy: "database",
maxAge: 30 * 24 * 60 * 60, // 30 días
updateAge: 24 * 60 * 60, // Actualizar cada día
},
})
Prisma necesita la tabla Session; npx prisma db push la crea:
// schema.prisma
model Session {
id String @id @default(cuid())
sessionToken String @unique
userId String
expires DateTime
user User @relation(fields: [userId], references: [id], onDelete: Cascade)
}
model User {
id String @id @default(cuid())
email String @unique
sessions Session[]
}
Trucos de rendimiento:
-
Indexa
sessionTokenyexpires; las consultas van mucho más rápido. -
Limpia Sessions vencidas con una tarea programada:
// cleanup-sessions.ts
async function cleanupExpiredSessions() {
await prisma.session.deleteMany({
where: {
expires: {
lt: new Date(),
},
},
})
}
// Ejecutar cada día a las 3:00
Marco de decisión en la práctica
Mucha teoría; ¿cómo elegir? Este marco te ayuda a repasar tu proyecto:
Por tipo de proyecto
| Tipo de proyecto | Recomendación | Motivo |
|---|---|---|
| App Serverless | JWT | Stateless, sin almacén compartido de Session |
| App monolítica tradicional | Session | Base de datos cerca, costo de consulta bajo |
| Microservicios | JWT o híbrido | Autenticación entre servicios más simple |
| Sitio de contenido/blog | JWT | Autenticación simple, JWT basta |
| Panel SaaS | Session | Control fino de permisos |
Por requisitos de seguridad
- Baja sensibilidad (blog, foro, contenido): JWT, expiración 30 días
- Sensibilidad media (e-commerce, social): JWT + expiración corta + refresh token
- Alta sensibilidad (finanzas, salud, panel admin): Session, expiración corta + sesión deslizante
Casos reales
Caso 1: tienda online — JWT
La tienda de mi amigo eligió JWT porque:
- Está en Vercel, arquitectura Serverless; JWT encaja mejor
- Muchos usuarios; menos consultas a BD reduce costos
- No necesitan control estricto de sesión; que el token tarde en caducar tras logout es aceptable
El compromiso: expiración de 7 días en lugar de 30. Si roban la cuenta, el token caduca en una semana.
Caso 2: sistema OA — Session
Un OA interno eligió Session porque:
- Permisos estrictos; los cambios de rol deben aplicar al instante
- Hay que limitar dispositivos con sesión simultánea
- Despliegue en intranet; la latencia de BD es irrelevante
Session encaja mejor. Optimizamos con Redis; la respuesta queda por debajo de 50 ms.
Caso 3: esquema híbrido — el enfoque de Clerk
Muchas plataformas de autenticación (como Clerk) usan un esquema híbrido:
- Token corto (JWT): 15 minutos, para llamadas API
- Token largo (Refresh Token): en base de datos, para renovar el corto
- Registro de Session: sesiones activas en BD, revocables a distancia
Rendimiento de JWT y control de Session. Más complejo de implementar; encaja cuando seguridad y UX exigen mucho.
Mi recomendación
Si sigues sin decidir:
-
JWT por defecto: en la mayoría de casos basta, es simple y rinde bien.
-
Cambia a Session si:
- Necesitas límite multi-dispositivo
- Necesitas expulsar usuarios al instante
- Finanzas/salud u otros escenarios de alta seguridad
- Tras cambiar contraseña, invalidar todas las sesiones de inmediato
-
No sobre-diseñes: si el proyecto es pequeño, empieza simple y optimiza cuando llegue el cuello de botella. He visto proyectos que arrancan con híbrido y la complejidad explota sin usar esas funciones.
Mejores prácticas para la expiración de sesión
Elegido el esquema, hay que gestionar bien la expiración. Mal hecho, la UX se resiente.
Expiración con JWT
Lo peor: el usuario rellena un formulario, envía y aparece «sesión expirada»; pierde todo. Mala experiencia.
Solución: Refresh Token + renovación transparente
La idea:
-
Dos tokens:
- Access Token: 15 minutos, para API
- Refresh Token: 30 días, para obtener un Access Token nuevo
-
El frontend renueva el Access Token cuando queda poco (por ejemplo 2 minutos), usando el Refresh Token
-
El usuario no nota nada; no hay cierre súbito
NextAuth.js trae este mecanismo; solo hay que configurarlo:
callbacks: {
async jwt({ token, user, account }) {
if (account && user) {
// Primer login
return {
...token,
accessToken: account.access_token,
accessTokenExpires: Date.now() + account.expires_in * 1000,
refreshToken: account.refresh_token,
}
}
// Access Token aún válido
if (Date.now() < token.accessTokenExpires) {
return token
}
// Access Token expirado: renovar con Refresh Token
return refreshAccessToken(token)
},
}
async function refreshAccessToken(token) {
try {
const response = await fetch("https://api.example.com/oauth/token", {
method: "POST",
headers: { "Content-Type": "application/x-www-form-urlencoded" },
body: new URLSearchParams({
client_id: process.env.OAUTH_CLIENT_ID,
grant_type: "refresh_token",
refresh_token: token.refreshToken,
}),
})
const refreshedTokens = await response.json()
return {
...token,
accessToken: refreshedTokens.access_token,
accessTokenExpires: Date.now() + refreshedTokens.expires_in * 1000,
refreshToken: refreshedTokens.refresh_token ?? token.refreshToken,
}
} catch (error) {
return {
...token,
error: "RefreshAccessTokenError",
}
}
}
Expiración con Session
Con Session es más sencillo, pero también hay matices.
Sesión deslizante vs tiempo absoluto
- Sesión deslizante: la actividad del usuario extiende la sesión; encaja en la mayoría de casos
- Tiempo absoluto: caduca a la hora fijada aunque haya actividad; banca y alta seguridad
La sesión deslizante en NextAuth.js se configura así:
session: {
strategy: "database",
maxAge: 2 * 60 * 60, // 2 horas de tiempo absoluto
updateAge: 30 * 60, // Actualizar si hay actividad en 30 min
}
Con actividad en 30 minutos, la sesión se alarga; pero a las 2 horas siempre caduca.
Timeout por inactividad
Para «cerrar sesión tras 15 minutos sin actividad», puedes usar un temporizador en el frontend:
let idleTimer
function resetIdleTimer() {
clearTimeout(idleTimer)
idleTimer = setTimeout(() => {
// 15 minutos sin actividad: cerrar sesión
signOut()
}, 15 * 60 * 1000)
}
// Escuchar actividad del usuario
document.addEventListener("mousemove", resetIdleTimer)
document.addEventListener("keypress", resetIdleTimer)
Tendencias en 2025
Algunas tendencias de este año (2025) que pueden influir en tu elección.
Lo híbrido es mainstream
Cada vez más equipos ven que JWT y Session no son excluyentes:
- JWT para autenticación API (rápido)
- Registro de Session activa en base de datos (control)
- Combinar lo mejor de ambos
Clerk, Auth0 y similares lo hacen así. Si no quieres implementarlo tú, estos servicios ayudan.
Impacto de Edge Runtime
El middleware de Next.js corre en Edge Runtime y favorece JWT. Si la autenticación va en middleware (por ejemplo proteger /dashboard), JWT resulta más cómodo.
Passkey y WebAuthn
No es el tema de hoy, pero conviene mencionarlo: Passkey (WebAuthn) se extiende; muchos sitios permiten huella o Face ID sin contraseña. El flujo se complica un poco, pero la UX mejora. NextAuth.js v5 ya soporta Passkey.
Para cerrar
Deberías tener ya una idea clara de JWT y Session. No hay respuesta única: entiende pros y contras y decide según tu proyecto.
Mi experiencia: en la mayoría de proyectos JWT basta; Session cuando necesitas control inmediato. No empieces demasiado complejo; lo simple suele ser más fiable.
Si sigues dudando, usa la configuración por defecto de NextAuth.js (JWT) y arranca. Si surge un problema, pasar a Session no cuesta mucho: unos cambios de configuración.
¿Sigues a las tres de la mañana eligiendo stack? Duerme primero y decide al día siguiente. A veces, tras dormir, el problema deja de serlo.
Si tienes ideas sobre este artículo o un escenario que no cubrí, comenta. La tecnología se entiende mejor hablando entre todos.
FAQ
¿Cuál es la diferencia clave entre JWT y Session?
¿Cuándo usar JWT y cuándo Session?
JWT no se revoca al instante: ¿qué hago?
¿Cómo optimizar el rendimiento de Session?
¿Cómo configurar JWT y Session en NextAuth.js?
¿Cómo gestionar la expiración de sesión y mejorar la UX?
¿Qué tendencias hay en 2025 para JWT y Session?
12 min de lectura · Publicado el: 19 dic 2025 · Actualizado el: 21 ago 2026
Guía completa de Next.js
Si llegaste desde búsqueda, lo más rápido es ir al artículo anterior o siguiente de esta misma serie.
Anterior
Tutorial de NextAuth.js: guía completa de inicio de sesión con Credentials y gestión de sesiones
¿NextAuth.js te parece demasiado complejo? Este artículo se centra en el inicio de sesión con Credentials, la elección entre JWT y Session, ejemplos de código completos y errores habituales para que los desarrolladores de Next.js monten su sistema de autenticación rápido.
Parte 48 de 51
Siguiente
Guía completa de SWR: domina estrategias de caché y actualizaciones optimistas en la práctica
Aprende los conceptos clave y las estrategias de caché de SWR, y simplifica hasta el 90 % de tu código de obtención de datos con un solo Hook. Incluye ejemplos prácticos de actualizaciones optimistas, comparación con React Query y mejores prácticas de integración con Next.js.
Parte 50 de 51



Comentarios
Inicia sesión con GitHub para dejar un comentario