La mayoría de los errores de JWT en TypeScript no están en el token: están en la configuración de verificación. Una restricción de algoritmo ausente, un claim de emisor sin comprobar o un secreto de vida corta pueden minar silenciosamente la autenticación de una forma que solo se manifiesta en producción. Esta guía recorre cada dimensión de la corrección de configuración JWT en TypeScript: algoritmos, validación de claims, higiene de secretos, gestión de expiración y las herramientas de navegador que aceleran la depuración.
Qué cubre la validación de configuración JWT
Un JSON Web Token (JWT) es una cadena compacta y segura para URL compuesta por tres partes codificadas en Base64URL separadas por puntos: un encabezado que declara el algoritmo y el tipo de token, un payload que transporta los claims y una firma que los une. Validar un JWT significa confirmar que las tres partes están intactas y que los claims cumplen los requisitos de tu aplicación, no solo que la firma es matemáticamente correcta.
Las dos capas de la validación JWT
La validación criptográfica confirma la firma: el servidor verifica que el token fue firmado con la clave esperada y que no ha sido manipulado. La validación de configuración va más allá: comprueba que el token fue emitido por la autoridad correcta, está destinado a este servicio concreto, no ha expirado y transporta los claims personalizados esperados. La mayoría de las vulnerabilidades de seguridad JWT provienen de una validación de configuración incompleta, no de una criptografía rota.
- Algoritmo (`alg`) - debe coincidir exactamente con la configuración de tu servidor; nunca lo infieras del encabezado del token
- Expiración (`exp`) - el token no debe haber pasado su marca de tiempo de expiración, con tolerancia de reloj
- No-antes-de (`nbf`) - el token no debe usarse antes de su primera hora válida
- Emisor (`iss`) - el token debe provenir de tu servicio de autenticación de confianza
- Audiencia (`aud`) - el token debe estar destinado a esta API o servicio concreto
- Claims personalizados - rol, scope, ID de tenant o cualquier campo específico de la aplicación del que dependa tu lógica
Note
Comprobaciones de algoritmo y configuración de claves
La configuración del algoritmo es el ajuste más crítico para la seguridad en la verificación JWT. Equivocarse habilita una clase de ataques que omiten la autenticación por completo. Las librerías JWT de TypeScript te dan las herramientas para aplicarlo correctamente, pero solo si las usas de forma explícita.
HS256 vs RS256 - elegir el algoritmo correcto
| Propiedad | HS256 (simétrico) | RS256 (asimétrico) |
|---|---|---|
| Tipo de clave | Secreto compartido (misma clave para firmar y verificar) | Par de claves RSA (privada para firmar, pública para verificar) |
| Distribución de claves | Cada verificador posee el secreto | Solo el emisor posee la clave privada |
| Seguridad multi-servicio | ✗ Arriesgado - todos los verificadores pueden falsificar tokens | ✓ Los verificadores solo tienen la clave pública |
| Soporte OIDC / JWKS | ✗ No aplicable | ✓ Claves públicas servidas vía endpoint JWKS |
| Rendimiento | ✓ Rápido (HMAC) | ✗ Más lento (matemática RSA) |
| Mejor para | Herramientas internas, APIs de un solo servicio | APIs de producción, sistemas distribuidos, OIDC |
El ataque de confusión de algoritmo - y cómo prevenirlo
La confusión de algoritmo ocurre cuando un servidor lee el campo `alg` del encabezado JWT para decidir cómo verificar el token, en lugar de forzar el algoritmo desde su propia configuración. Un atacante modifica el encabezado para cambiar `RS256` por `HS256`, y luego firma el token con la clave pública del servidor usada como secreto HMAC. Un servidor mal configurado lo acepta como válido. La solución es una línea de código, pero debe estar presente.
// ✗ Vulnerable - algorithm inferred from token header
jwt.verify(token, publicKey);
// ✓ Correct - algorithm enforced from server configuration
jwt.verify(token, publicKey, { algorithms: ['RS256'] });Warning
Validación de la fuerza de la clave para HS256
Al usar HS256, el secreto debe tener al menos 256 bits (32 bytes) para coincidir con el tamaño de salida de SHA-256. Secretos cortos - menos de 32 caracteres, palabras de diccionario o cadenas estáticas como `"secret"` o `"development"` - se rompen por fuerza bruta trivialmente con herramientas como `hashcat` o `jwt_tool`. Genera secretos con una fuente aleatoria criptográficamente segura: `crypto.randomBytes(32).toString('hex')` en Node.js produce una cadena hexadecimal de 64 caracteres que cumple el requisito mínimo de entropía.
Validar claims JWT estándar en TypeScript
La especificación JWT define un conjunto de claims registrados estándar que toda implementación debería entender. La librería `jsonwebtoken` valida varios de ellos automáticamente cuando pasas las opciones correctas, pero la clave está en "cuando las pasas". Sin configuración explícita, la mayoría de las comprobaciones de claims se omiten en silencio.
Los claims exp, nbf e iat
El claim exp (expiración) es una marca de tiempo Unix tras la cual el token deja de ser válido. La librería jsonwebtoken comprueba exp por defecto durante jwt.verify(). Sin embargo, la desincronización de relojes entre el emisor del token y el verificador puede hacer que tokens válidos sean rechazados, un origen común de errores de "token expirado" en sistemas distribuidos donde los relojes de los servidores derivan por segundos. Pasa clockTolerance para permitir una ventana pequeña: establecer clockTolerance en 30 acepta tokens hasta 30 segundos después de su valor exp.
interface JwtPayload {
sub: string;
iss: string;
aud: string;
exp: number;
iat: number;
role: 'admin' | 'user';
}
const payload = jwt.verify(token, publicKey, {
algorithms: ['RS256'],
issuer: 'https://auth.example.com',
audience: 'api.example.com',
clockTolerance: 30, // seconds of clock skew to tolerate
}) as JwtPayload;Los claims iss y aud
El claim `iss` (emisor) identifica el origen del token. El claim `aud` (audiencia) identifica el destinatario previsto. Ambos son opcionales en la especificación JWT pero críticos en la práctica. Sin validación de `iss`, cualquier servicio que pueda producir tokens válidos con tu clave de firma puede autenticarse en tu API. Sin validación de `aud`, un token emitido para tu app móvil puede reutilizarse contra tu API de administración. Pasa ambos como opciones a `jwt.verify()` para que la librería los imponga como requisitos estrictos y no como campos informativos.
Tip
Implementar la validación JWT en TypeScript
Una función completa de verificación JWT en TypeScript maneja validación criptográfica, validación de claims y clasificación de errores en un solo lugar. Así se estructura, con los cuatro pasos que corresponden al esquema HowTo.
Verifica que el algoritmo coincida con tu tipo de clave
Antes de escribir código de verificación, confirma tu combinación de algoritmo y clave. RS256 requiere una clave privada RSA para firmar y la clave pública correspondiente para verificar. HS256 requiere el mismo secreto compartido en ambos lados. Confundirlos provoca excepciones en tiempo de ejecución difíciles de diagnosticar. Guarda tu clave pública o secreto compartido en variables de entorno; nunca los incrustes en archivos fuente.
Valida explícitamente los claims exp y nbf
Establece siempre `clockTolerance` para manejar pequeñas derivas de reloj entre servicios. Un valor de 30 segundos es un valor por defecto razonable para la mayoría de los sistemas distribuidos. Al depurar `TokenExpiredError` en producción, registra `payload.exp * 1000` y `Date.now()` juntos: esto muestra exactamente cuántos milisegundos pasó el token de su expiración, lo que distingue una expiración real de un problema de sincronización de relojes entre tu servicio de autenticación y el servidor API.
Comprueba los claims iss y aud contra los valores esperados
Pasa las opciones `issuer` y `audience` a `jwt.verify()` para que la librería rechace tokens con valores no coincidentes antes de que se ejecute tu código de aplicación. Si tu sistema tiene múltiples audiencias válidas (por ejemplo, tanto `api.example.com` como `admin.example.com`), pasa un array: `audience: ['api.example.com', 'admin.example.com']`. La librería acepta el token si el claim `aud` coincide con cualquier entrada del array.
Prueba tu configuración con el Decodificador JWT
Antes de ejecutar tu suite de pruebas TypeScript, pega un token de muestra de tu entorno de desarrollo o staging en el Decodificador y Validador JWT. Confirma visualmente que cada claim - `alg`, `exp`, `iss`, `aud` y tus claims personalizados - coincide con las opciones de tu `jwt.verify()`. Esta comprobación de un minuto detecta discrepancias entre lo que contiene el token y lo que espera tu código antes de invertir tiempo en depurar en un entorno de pruebas.
Decodificador y Validador JWT
Decodifica tokens JWT e inspecciona todos los claims, campos del encabezado y problemas de seguridad comunes al instante en tu navegador - sin registro, sin subidas al servidor.
Errores comunes de configuración JWT
Estos son los errores de configuración que aparecen con más frecuencia en implementaciones JWT de TypeScript. Cada uno es silencioso al arrancar y solo se manifiesta como un fallo de autenticación o un incidente de seguridad en producción.
Usar `jwt.decode()` en lugar de `jwt.verify()`
La función `jwt.decode()` extrae el payload sin verificar la firma. Es útil para inspeccionar un token en el que ya confías - como extraer un ID de usuario de un token ya validado por middleware. No es un sustituto de `jwt.verify()`. El código que usa `jwt.decode()` para obtener claims y luego toma decisiones de autorización basadas en esos claims está aceptando tokens no verificados. Es una omisión completa de la autenticación.
Ignorar el tipo de error en los bloques catch
La librería `jsonwebtoken` lanza tres tipos de error distintos: `JsonWebTokenError` (token malformado o firma inválida), `TokenExpiredError` (pasado el claim `exp`) y `NotBeforeError` (antes del claim `nbf`). Capturar todos los errores como un `Error` genérico y devolver `401 Unauthorized` para todos los casos pierde información de diagnóstico. Maneja cada tipo por separado y devuelve mensajes específicos - `token expirado` frente a `token inválido` - para que los clientes y los sistemas de monitoreo distingan problemas de configuración de intentos de ataque genuinos.
No rotar secretos o pares de claves
Los secretos de firma de larga vida acumulan riesgo con el tiempo. Un secreto que nunca se ha rotado significa que cada token emitido con él sigue siendo válido si el secreto se ve comprometido. Implementa un campo de ID de clave (`kid`) en tu encabezado JWT para que el verificador pueda buscar la clave pública correcta en un endpoint JWKS. Este patrón permite la rotación de claves sin invalidar tokens firmados por la clave anterior: cada token lleva una referencia a la clave específica que lo firmó. El Inspector de JWK ayuda a validar la salida del endpoint JWKS y detectar material de clave privada que no debería estar expuesto públicamente.
Warning
Aceptar el algoritmo `none`
El algoritmo `none` produce un JWT sin firma - cualquier payload con estructura válida pasa la verificación. Las primeras versiones de las librerías JWT aceptaban `none` por defecto. Las librerías modernas lo rechazan, pero solo cuando especificas explícitamente los algoritmos permitidos en las opciones de verificación. Incluye siempre `algorithms: ['RS256']` (o tu algoritmo concreto) para hacer explícito el rechazo de `none` y resistir cambios de versión de la librería.
Depurar problemas JWT con herramientas de navegador
Escribir una prueba para reproducir un error JWT suele ser más lento que inspeccionar el token directamente. Las herramientas de navegador te permiten examinar los claims, el estado de expiración y la estructura de claves de un token en segundos, sin entorno local, sin ejecutar código y sin subir datos sensibles a un servicio de terceros.
Decodificar e inspeccionar claims
El Decodificador y Validador JWT decodifica el encabezado y el payload de cualquier cadena JWT y presenta todos los claims en un formato estructurado y legible. Comprueba problemas de seguridad comunes - `exp` ausente, algoritmo débil, `aud` ausente - y los señala con diagnósticos claros. Pega cualquier token de tu entorno de desarrollo, staging o producción y confirma en diez segundos si los claims coinciden con lo que espera tu llamada a `jwt.verify()`.
Diagnosticar problemas de expiración
La Calculadora de Cuenta Atrás de Expiración JWT lee el claim `exp` de cualquier token y muestra la vida útil restante exacta o el tiempo desde la expiración tanto en UTC como en hora local. Cuando un usuario informa de "token expirado" pero tus logs muestran que el token debería seguir siendo válido, pégalo en la calculadora. La comparación de marcas de tiempo a nivel de milisegundo revela si el problema es una expiración genuina, una deriva de reloj entre servicios o un valor `exp` almacenado en milisegundos en lugar de segundos - un bug de factor-1000 sorprendentemente común.
Inspeccionar conjuntos de claves JWKS
Al validar tokens de un proveedor OIDC o de cualquier servicio que publique un endpoint JWKS, el Inspector de JWK analiza y valida la estructura del conjunto de claves. Comprueba que cada clave tenga los campos requeridos (`kty`, `use`, `alg`, `kid`), valida el tipo de clave y la curva para claves EC, y señala cualquier material de clave privada que no debería estar expuesto públicamente. Pega el JSON de tu endpoint JWKS directamente en la herramienta o proporciona la URL para que se obtenga.
Calculadora de Cuenta Atrás de Expiración JWT
Calcula la cuenta atrás exacta de expiración JWT desde el claim exp - mira el tiempo restante o el tiempo desde la expiración en UTC y hora local, sin código.
Buenas prácticas de validación JWT
Una función de verificación JWT correctamente configurada es necesaria pero no suficiente para una autenticación segura. Estas prácticas completan el panorama para servicios TypeScript de producción.
Usa una interfaz de payload tipada
Define una interfaz TypeScript para tu payload JWT y convierte el resultado de verificación a ella. Esto te da seguridad en tiempo de compilación sobre nombres de claims y tipos de valores: un nombre de claim mal escrito (`userId` frente a `user_id`) se convierte en un error de TypeScript en lugar de un `undefined` silencioso en tiempo de ejecución. Mantén la interfaz en un módulo de tipos compartido para que sea consistente en todos los servicios que verifican el mismo formato de token.
Vidas cortas de token con refresh tokens
Los tokens de acceso deben tener vidas cortas: de 15 minutos a 1 hora para la mayoría de las APIs. Los tokens de acceso de larga vida (días, semanas) aumentan la ventana durante la cual un token comprometido puede usarse. Usa un flujo separado de refresh token con expiración larga para la persistencia de sesión. El refresh token rota en cada uso, y las listas de revocación solo se necesitan para los refresh tokens - no para los de acceso - cuando las vidas se mantienen cortas.
Centraliza la lógica de verificación
Escribe la verificación JWT en un solo lugar - una función de middleware o una utilidad compartida - y úsala en todas partes. La lógica de verificación duplicada invita a la deriva de configuración: un endpoint comprueba `aud`, otro olvida hacerlo, y la inconsistencia se explota antes de que alguien se dé cuenta. El middleware de Express, los guards de NestJS y los manejadores de rutas de Next.js tienen patrones limpios para centralizar las comprobaciones de autenticación. Pon tus opciones `algorithms`, `issuer` y `audience` en un único objeto de configuración importado en todo el código base.
Configura la verificación una vez, aplícala en todas partes. La única opción JWT que debería diferir entre endpoints es la audiencia esperada.
Tip
Key takeaways
- Pasa siempre `algorithms: ['RS256']` (o tu algoritmo concreto) explícitamente a `jwt.verify()` - nunca dejes que la librería lo infiera del encabezado del token.
- Valida los claims `iss` y `aud` en cada llamada de verificación; omitirlos permite que tokens de otros servicios se autentiquen contra tu API.
- Usa `clockTolerance` para manejar la deriva de relojes entre servicios distribuidos, y registra las marcas de tiempo `exp` junto a `Date.now()` al depurar errores de expiración.
- Nunca uses `jwt.decode()` para decisiones de autorización - se salta por completo la verificación de firma y acepta cualquier payload de token.
- El Decodificador y Validador JWT te permite inspeccionar todos los claims y señalar problemas de configuración en segundos, sin escribir ni ejecutar código.
- RS256 es preferible a HS256 para APIs de producción - elimina el riesgo de distribución de secretos compartidos y soporta rotación de claves basada en JWKS.
- Mantén vidas cortas de los tokens de acceso (15-60 minutos) y centraliza toda la lógica de verificación en un único middleware o utilidad para prevenir la deriva de configuración.