Buenas prácticas y errores comunes en JWT
Los JWT son engañosamente simples: así evitas las vulnerabilidades, las trampas de rendimiento y los errores de arquitectura más habituales.

Los JSON Web Tokens están en todas partes: autenticación, autorización de APIs, gestión de sesiones. Su simplicidad resulta atractiva: un payload autocontenido y firmado que codifica información del usuario. Pero esa simplicidad esconde un campo minado de vulnerabilidades de seguridad y decisiones de arquitectura que la mayoría de los tutoriales pasan por alto.
Qué es realmente un JWT
Un JWT son tres objetos JSON codificados en base64url y separados por puntos: encabezado, payload y firma.
// Header: algorithm and token type
{ "alg": "RS256", "typ": "JWT" }
// Payload: claims (data)
{
"sub": "user_123",
"name": "Alice",
"role": "admin",
"iat": 1587654321,
"exp": 1587657921
}
// Signature: cryptographic proof that the header and payload are unmodified
// RSASHA256(base64url(header) + "." + base64url(payload), privateKey)La firma garantiza la integridad — si alguien modifica el payload, la firma deja de coincidir. Pero no cifra nada. Cualquiera que tenga el token puede leer el payload.
Ataques de confusión de algoritmo
La vulnerabilidad más peligrosa de JWT es la manipulación del encabezado alg. Algunas bibliotecas confían en el algoritmo indicado dentro del propio token en lugar de imponer uno desde el servidor.
// ❌ Trusting the algorithm from the token header
const payload = jwt.verify(token, publicKey);
// An attacker can set alg: "none" and skip the signature entirely
// Or set alg: "HS256" and sign with the public key as the HMAC secret
// ✅ Always specify the expected algorithm
const payload = jwt.verify(token, publicKey, {
algorithms: ["RS256"], // Only accept RS256
});Restringe siempre los algoritmos aceptados. Nunca permitas "none". Si usas firma asimétrica (RS256), nunca aceptes firma simétrica (HS256).
Firma simétrica frente a asimétrica
| Algoritmo | Firma | Verificación | Caso de uso |
|---|---|---|---|
| HS256 | Secreto compartido | El mismo secreto compartido | Un solo servicio |
| RS256 | Clave privada | Clave pública | Microservicios, verificación por terceros |
| ES256 | Clave privada | Clave pública | Igual que RS256, con firmas más pequeñas |
// HS256: same secret signs and verifies
// ❌ Every service that verifies tokens knows the signing secret
const token = jwt.sign(payload, "shared-secret", { algorithm: "HS256" });
jwt.verify(token, "shared-secret", { algorithms: ["HS256"] });
// RS256: only the auth service has the private key
// ✅ Other services verify with the public key — can't forge tokens
const token = jwt.sign(payload, privateKey, { algorithm: "RS256" });
jwt.verify(token, publicKey, { algorithms: ["RS256"] });Usa RS256 o ES256 cuando varios servicios necesiten verificar tokens. Con HS256, cualquier servicio que pueda verificar también puede falsificar tokens.
Mantén los payloads pequeños
Los JWT se envían en cada solicitud, ya sea en encabezados, cookies o parámetros de URL. Un payload sobrecargado desperdicia ancho de banda y puede superar los límites de tamaño de los encabezados.
// ❌ Stuffing everything into the JWT
const token = jwt.sign({
sub: "user_123",
name: "Alice Johnson",
email: "alice@example.com",
role: "admin",
permissions: ["read", "write", "delete", "manage-users", "view-analytics"],
organization: { id: "org_456", name: "Acme Corp", plan: "enterprise" },
preferences: { theme: "dark", language: "en", timezone: "UTC" },
}, key);
// This token is 500+ bytes — sent with EVERY request
// ✅ Minimal payload — look up details when needed
const token = jwt.sign({
sub: "user_123",
role: "admin",
org: "org_456",
}, key, { expiresIn: "15m" });
// ~200 bytes — essential claims onlyIncluye el ID del usuario, el rol y cualquier dato necesario para tomar decisiones de autorización. Todo lo demás puede consultarse en una base de datos o una caché cuando haga falta.
Expiración y revocación
Los JWT son sin estado — una vez emitidos, el servidor no puede revocarlos sin infraestructura adicional. Esta es la disyuntiva fundamental.
// Short expiration limits the damage window
const accessToken = jwt.sign(
{ sub: userId, role: user.role },
privateKey,
{
algorithm: "RS256",
expiresIn: "15m", // Short-lived
issuer: "https://auth.myapp.com",
audience: "https://api.myapp.com",
},
);// For immediate revocation, you need a blocklist
const revokedTokens = new Set<string>();
function verifyToken(token: string) {
const payload = jwt.verify(token, publicKey, {
algorithms: ["RS256"],
issuer: "https://auth.myapp.com",
audience: "https://api.myapp.com",
});
// Check if this specific token has been revoked
if (revokedTokens.has(payload.jti)) {
throw new Error("Token revoked");
}
return payload;
}Una lista de bloqueo es una solución de compromiso — se pierde la ausencia de estado pura, pero se gana la capacidad de revocar. Guarda la lista de bloqueo en Redis con un TTL que coincida con la vida máxima del token, para que las entradas expiren solas.
Valida todo
// ❌ Minimal verification — many attack vectors remain
const payload = jwt.verify(token, key);
// ✅ Full verification — close every loophole
const payload = jwt.verify(token, publicKey, {
algorithms: ["RS256"], // Prevent algorithm confusion
issuer: "https://auth.myapp.com", // Reject tokens from other issuers
audience: "https://api.myapp.com", // Reject tokens meant for other services
clockTolerance: 30, // Allow 30s clock skew
maxAge: "1h", // Reject tokens older than 1 hour
});
// Additional checks
if (!payload.sub) throw new Error("Missing subject claim");
if (!payload.role) throw new Error("Missing role claim");Cuándo no usar JWT
Los JWT no siempre son la mejor opción. Si tu aplicación corre en un solo servidor y no necesita verificar tokens entre distintos servicios, las sesiones del lado del servidor son más simples y más seguras.
| Escenario | JWT | Sesión del lado del servidor |
|---|---|---|
| Un solo servidor | Complejidad innecesaria | ✅ Más simple |
| Microservicios | ✅ No requiere almacén de sesiones compartido | Requiere un almacén compartido |
| Necesita revocación instantánea | Requiere lista de bloqueo | ✅ Basta con eliminar la sesión |
| Autenticación entre dominios | ✅ Autocontenido | Requiere compartir cookies |
Puntos clave
- Especifica siempre el algoritmo al verificar — nunca confíes en el encabezado
algdel token - Usa RS256/ES256 en arquitecturas con varios servicios — con HS256, cualquier verificador puede falsificar tokens
- Mantén los payloads mínimos — solo el ID del usuario, el rol y los datos imprescindibles para la autorización
- Una expiración corta (15 min) más tokens de refresco limita la ventana de daño si roban un token
- Los JWT no se pueden revocar sin una lista de bloqueo — esa es la disyuntiva fundamental de su naturaleza sin estado
- Valida el emisor, la audiencia y el algoritmo en cada verificación — no solo la firma


