Cambiar tema

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

Easton editorial illustration: rendering-mode selector

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.

0
Consultas a BD con JWT
Las peticiones posteriores no consultan BD
Cada una
Consulta a BD con Session
Cada petición consulta BD
15 min
Expiración recomendada JWT
Combinado con refresh token
4 KB
Límite de tamaño de cookie
El token JWT no puede ser muy grande
Source: Resumen de experiencia práctica

JWT en profundidad

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:

  1. Configura NEXTAUTH_SECRET (en Auth.js v5 se llama AUTH_SECRET). Genera una clave segura con npx auth secret. Sirve para firmar y cifrar el JWT; no la compartas.

  2. 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.

  1. 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:

  1. Guardar Session en Redis: mucho más rápido que una base de datos tradicional y con expiración automática.

  2. Minimizar datos de Session: solo el ID de usuario; el resto, cuando haga falta.

  3. 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:

  1. Indexa sessionToken y expires; las consultas van mucho más rápido.

  2. 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 proyectoRecomendaciónMotivo
App ServerlessJWTStateless, sin almacén compartido de Session
App monolítica tradicionalSessionBase de datos cerca, costo de consulta bajo
MicroserviciosJWT o híbridoAutenticación entre servicios más simple
Sitio de contenido/blogJWTAutenticación simple, JWT basta
Panel SaaSSessionControl 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:

  1. JWT por defecto: en la mayoría de casos basta, es simple y rinde bien.

  2. 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
  3. 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:

  1. Dos tokens:

    • Access Token: 15 minutos, para API
    • Refresh Token: 30 días, para obtener un Access Token nuevo
  2. El frontend renueva el Access Token cuando queda poco (por ejemplo 2 minutos), usando el Refresh Token

  3. 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?
JWT es stateless: la información del usuario va codificada en el token y el servidor solo valida la firma, sin consultar la base de datos. Session es stateful: la información vive en la base de datos y cada petición la valida. JWT encaja en Serverless/alta concurrencia; Session, cuando necesitas control inmediato.
¿Cuándo usar JWT y cuándo Session?
JWT: Serverless/edge, alta concurrencia, microservicios, sitios de contenido/blog. Session: control inmediato (límite multi-dispositivo, cierre forzado), alta seguridad (finanzas/salud), apps monolíticas tradicionales. Por defecto, JWT; cambia a Session si necesitas control inmediato.
JWT no se revoca al instante: ¿qué hago?
Un JWT emitido sigue válido hasta que expira. Soluciones: 1) expiración corta (15 min) + refresh token, 2) lista negra (pierdes la ventaja stateless), 3) Session o esquema híbrido. En la mayoría de casos, expiración corta + refresh token basta.
¿Cómo optimizar el rendimiento de Session?
Session consulta la base de datos en cada petición y puede ser un cuello de botella. Optimiza: 1) guardar Session en Redis (mucho más rápido), 2) minimizar datos (solo ID de usuario), 3) indexar sessionToken y expires, 4) limpiar Sessions vencidas con regularidad.
¿Cómo configurar JWT y Session en NextAuth.js?
JWT: session: { strategy: "jwt", maxAge: 30 días }, añade datos de usuario en callbacks.jwt. Session: usa PrismaAdapter, session: { strategy: "database" }, crea la tabla Session. En Edge Runtime hay que dividir la configuración.
¿Cómo gestionar la expiración de sesión y mejorar la UX?
JWT: Access Token (15 min) + Refresh Token (30 días) para renovación transparente; el frontend renueva antes de expirar. Session: sesión deslizante (updateAge) que se extiende con actividad. Evita que el usuario pierda un formulario por un cierre de sesión súbito.
¿Qué tendencias hay en 2025 para JWT y Session?
Lo híbrido es mainstream: JWT para API (rápido) + Session en base de datos (controlable). Edge Runtime favorece JWT en middleware. Passkey/WebAuthn se extiende; NextAuth.js v5 ya lo soporta. Elige según necesidad real, sin sobre-diseñar.

12 min de lectura · Publicado el: 19 dic 2025 · Actualizado el: 21 ago 2026

Comentarios

Inicia sesión con GitHub para dejar un comentario

Easton BlogEaston Blog