jwtsecurityauthentication

Mejores prácticas de seguridad JWT para 2026

· Cosyslabs

Las fallas de seguridad en JWT se encuentran consistentemente entre las principales vulnerabilidades de API. Use los algoritmos RS256 o ES256, rechace explícitamente el algoritmo none, configure el tiempo de expiración del token de acceso en menos de 15 minutos, implemente la rotación de tokens de actualización y siempre verifique las firmas en el servidor antes de confiar en cualquier claim.

¿Qué es un JWT?

Un JSON Web Token son tres segmentos codificados en Base64URL separados por puntos:

cabecera.payload.firma
eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIn0.firma
  • Cabecera: algoritmo y tipo de token
  • Payload: claims (datos del usuario, expiración, etc.)
  • Firma: prueba criptográfica de que el token no fue alterado

La firma es lo que debes verificar. Un JWT sin firma verificada es simplemente JSON no verificado.

Vulnerabilidad crítica: Confusión de algoritmo

El ataque del algoritmo none

Las primeras bibliotecas JWT aceptaban alg: "none" en la cabecera, lo que significa que no se requería firma. Un atacante podía forjar cualquier payload:

{
  "alg": "none",
  "typ": "JWT"
}

Sin verificación de firma, podían afirmar ser cualquier usuario. Solución:

// Node.js — siempre especifica explícitamente los algoritmos permitidos
jwt.verify(token, clavePublica, { algorithms: ["RS256"] });

// Nunca permitir "none"
// Nunca usar algorithms: ["RS256", "none"] — esto es una vulnerabilidad

Ataque de confusión RS256 vs HS256

HS256 (HMAC) usa un secreto compartido — la misma clave firma y verifica. RS256 (RSA) usa un par de claves — la clave privada firma, la clave pública verifica.

El ataque: si una biblioteca ve alg: "HS256" y usa la clave pública RS256 como secreto HMAC, un atacante que obtuvo la clave pública (¡que es pública!) puede forjar tokens.

Siempre fija el algoritmo en el servidor. Nunca leas alg del token para decidir cómo verificarlo.

// Vulnerable — lee alg del token
function verificar(token, clave) {
  const { alg } = decodificarCabecera(token);
  return verificarCon(token, clave, alg); // NUNCA hagas esto
}

// Seguro — el algoritmo está codificado en el servidor
function verificar(token) {
  return jwt.verify(token, CLAVE_PUBLICA, { algorithms: ["RS256"] });
}

Recomendaciones de algoritmos

AlgoritmoTipoCaso de uso
ES256Asimétrico (ECDSA)Mejor para proyectos nuevos — firmas pequeñas, rápido
RS256Asimétrico (RSA)Ampliamente soportado, bueno para interoperabilidad
HS256Simétrico (HMAC)Solo cuando el secreto es verdaderamente compartido y nunca expuesto
noneNingunoNunca usar

Para APIs públicas donde múltiples servicios verifican tokens, usa algoritmos asimétricos (ES256/RS256). La clave privada permanece en el servidor de autenticación; todos los demás servicios solo tienen la clave pública.

Expiración de tokens y estrategia de actualización

Los tokens de acceso de corta duración limitan el daño del robo de tokens. Usa un patrón de dos tokens:

  • Token de acceso: expira en 5–15 minutos, enviado con cada solicitud API
  • Token de actualización: expira en 7–30 días, almacenado de forma segura, usado solo para obtener nuevos tokens de acceso
// Emitir tokens
const tokenAcceso = jwt.sign(
  { sub: usuario.id, role: usuario.rol },
  CLAVE_PRIVADA,
  { algorithm: "ES256", expiresIn: "15m" }
);

const tokenActualizacion = jwt.sign(
  { sub: usuario.id, jti: crypto.randomUUID() },
  SECRETO_ACTUALIZACION,
  { expiresIn: "7d" }
);

Rotación de tokens de actualización

Cada vez que se usa un token de actualización, invalídalo y emite uno nuevo. Si se detecta que un token de actualización robado se usa dos veces, invalida toda la sesión:

async function actualizarTokens(tokenActualizacionAntiguo) {
  const payload = jwt.verify(tokenActualizacionAntiguo, SECRETO_ACTUALIZACION);
  
  // Verificar que el token no se haya usado antes (detección de reutilización)
  const registroToken = await db.tokensActualizacion.findOne({ jti: payload.jti });
  
  if (!registroToken || registroToken.usado) {
    // Reutilización de token detectada — revocar toda la familia
    await db.tokensActualizacion.revocarFamilia(payload.sub);
    throw new Error("Reutilización de token detectada");
  }
  
  // Marcar como usado
  await db.tokensActualizacion.marcarUsado(payload.jti);
  
  // Emitir nuevo par
  return emitirParDeTokens(payload.sub);
}

Almacenamiento seguro de JWTs

AlmacenamientoRiesgo XSSRiesgo CSRFRecomendación
localStorageAltoNingunoNunca para tokens de autenticación
sessionStorageAltoNingunoNunca para tokens de autenticación
Memoria (var JS)BajoNingunoBueno para tokens de acceso
Cookie HttpOnlyNingunoMedioMejor para tokens de actualización + tokens CSRF

Almacena los tokens de acceso en la memoria de JavaScript. Almacena los tokens de actualización en cookies HttpOnly, Secure, SameSite=Strict.

// Establecer token de actualización como cookie HttpOnly
res.cookie("refresh_token", tokenActualizacion, {
  httpOnly: true,
  secure: true,
  sameSite: "strict",
  maxAge: 7 * 24 * 60 * 60 * 1000, // 7 días
  path: "/auth/refresh", // solo enviado al endpoint de actualización
});

Claims que siempre debes validar

Además de verificar la firma, valida estos claims estándar:

jwt.verify(token, CLAVE_PUBLICA, {
  algorithms: ["ES256"],
  issuer: "https://auth.tudominio.com",    // claim iss
  audience: "https://api.tudominio.com",   // claim aud
  // exp se verifica automáticamente
  // nbf se verifica automáticamente
});
ClaimSignificadoValidar siempre
expTiempo de expiración
nbfNo antes de
issEmisor
audAudiencia
subSujeto (ID de usuario)Sí, verificar en sesión
jtiID de JWTSí, para revocación

Revocación de JWT

Los JWTs son sin estado — un token válido permanece válido hasta su expiración. Para revocación inmediata (cierre de sesión, cambio de contraseña, suspensión de cuenta), mantén una lista de bloqueo:

// Al cerrar sesión
await redis.setex(`revocado:${payload.jti}`, segundosTtlToken, "1");

// En cada solicitud
async function verificarToken(token) {
  const payload = jwt.verify(token, CLAVE_PUBLICA, { algorithms: ["ES256"] });
  
  const estaRevocado = await redis.exists(`revocado:${payload.jti}`);
  if (estaRevocado) throw new Error("Token revocado");
  
  return payload;
}

La corta expiración del token de acceso reduce cuánto tiempo necesitas mantener la lista de bloqueo.

Depuración de JWTs

Usa la Herramienta Decodificador JWT para inspeccionar cabeceras y payloads sin enviar tokens a servicios externos. Todo el descodificado ocurre en tu navegador.

Lista de verificación

  • Usar ES256 o RS256 — nunca HS256 para APIs públicas
  • Rechazar explícitamente alg: "none" en la configuración de la biblioteca
  • Fijar el algoritmo en el servidor — nunca leerlo de la cabecera del token
  • Establecer la expiración del token de acceso en 5–15 minutos
  • Implementar rotación de tokens de actualización con detección de reutilización
  • Almacenar tokens de actualización en cookies HttpOnly, tokens de acceso en memoria
  • Validar iss, aud, exp, nbf en cada solicitud
  • Implementar revocación basada en JTI para cierre de sesión/cambio de contraseña
  • Nunca registrar JWTs completos — son credenciales de portador

Más herramientas de Cosyslabs

  • Rough Estimator — Estima el esfuerzo de desarrollo e implementar autenticación JWT, rotación de tokens de actualización y capas de API seguras en tu proyecto.
  • Routine Toolkit — Utilidades del día a día incluyendo calculadora de préstamos, calculadora de fechas y contador de palabras.
  • Cosyslabs — El estudio detrás de Dev Tools !, PDF Convert All, Unit Convert All, y más.