Guide
Tokens JWT: Cómo decodificar, verificar y evitar los errores comunes
Tres segmentos base64url, una firma que debes verificar realmente, y un error histórico — alg: none — que todavía se incluye en algunas bibliotecas.
By Buğra SözeriPublished
Un JWT es tres cadenas codificadas en base64url unidas por puntos. La primera es metadatos, la segunda es los datos que realmente te interesan, y la tercera es la firma que prueba que las dos primeras no han cambiado. La mayoría de los errores JWT provienen de tratar la tercera parte como opcional — o de malinterpretar lo que garantiza el formato en primer lugar.
La estructura de tres segmentos
Un JWT se ve así:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwiaWF0IjoxNzQ4NjQ4MDAwfQ.qZdfL_HxR_eRT3z3qZX7Rqv0kK7r0sQYMfRBlLcM2hIDividido por los puntos, obtienes encabezado, payload, firma. Cada uno de los dos primeros es un objeto JSON que ha sido codificado en base64url — una variante de base64 segura para URL que reemplaza + con -, / con _, y omite el relleno. Pega cualquier token en nuestro decodificador JWT para ver las tres partes infladas a JSON plano.
El encabezado
{
"alg": "HS256",
"typ": "JWT"
}Dos campos. alg nombra el algoritmo de firma; cubrimos los valores a continuación. typ identifica el token como JWT. Algunos tokens también incluyen kid (ID de clave) para que el verificador sepa qué clave pública usar.
El payload
{
"sub": "1234567890",
"iat": 1748648000,
"exp": 1748651600,
"iss": "https://auth.example.com",
"aud": "https://api.example.com"
}El objeto JSON que elijas. Los campos se llaman reclamaciones. RFC 7519 define siete nombres de reclamaciones “registradas” que debes reutilizar cuando su significado coincida con el tuyo.
La firma
La firma se calcula sobre base64url(encabezado) + “.“ + base64url(payload) usando el algoritmo y la clave indicados por el encabezado. Es lo que hace que un JWT sea un JWT en lugar de un simple sobre base64.
Las reclamaciones registradas
- iss (emisor). Quién acuñó el token. Normalmente una URL que identifica tu servicio de autenticación. Los verificadores deben comprobar que coincide con el emisor esperado.
- sub (sujeto). De quién trata el token — normalmente el ID del usuario.
- aud (audiencia). Para quién es el token — la URL de la API o el nombre del servicio. Un verificador que no coincida con la audiencia esperada debe rechazar el token.
- exp (expiración). Timestamp Unix (segundos desde 1970-01-01 UTC) después del cual el token no es válido. Los verificadores deben rechazar tokens expirados.
- nbf (no antes de). Timestamp Unix antes del cual el token no es válido. Opcional.
- iat (emitido en). Timestamp Unix cuando se emitió el token. Informativo.
- jti (ID JWT). Un ID único para el propio token, útil para revocación explícita o para prevenir la reproducción.
HS256 vs RS256 vs ES256
HS256 (HMAC-SHA-256, simétrico)
La firma es un HMAC sobre el encabezado y payload codificados usando un secreto compartido. Cualquiera que pueda verificar la firma también puede crear una nueva — la misma clave hace ambos trabajos.
Usa HS256 cuando el emisor y el verificador son la misma parte. No uses HS256 cuando terceros necesiten verificar tus tokens — tendrías que compartir el secreto de firma, lo que significa que podrían acuñar tokens que parezcan tuyos.
RS256 (RSA-SHA-256, asimétrico)
La firma es una firma RSA usando la clave privada del emisor. Los verificadores usan la clave pública del emisor — publicada en un endpoint JWKS, típicamente — para verificar la firma. Los verificadores no pueden acuñar tokens.
Este es el predeterminado para identidad federada: OpenID Connect, Auth0, Cognito, cada flujo de “iniciar sesión con X”.
ES256 (ECDSA sobre P-256, asimétrico)
Mismo modelo de seguridad que RS256 pero con firmas de curva elíptica. Las ventajas: ~10x menor tamaño de firma (64 bytes vs 256), verificación más rápida y mayor seguridad por bit. La desventaja: las implementaciones de ECDSA son más fáciles de implementar incorrectamente que RSA.
Usa ES256 con una biblioteca probada (el crypto integrado de Node, crypto/ecdsa de Go, ring de Rust, cryptography de Python). No implementes ECDSA por tu cuenta. Nunca.
EdDSA (Ed25519)
Más nuevo que ES256, más difícil de usar incorrectamente, el más rápido de las opciones asimétricas. Listado en RFC 8037 como algoritmo JWT pero no universalmente compatible.
El ataque alg: none
La especificación JWT define un valor de algoritmo especial none que significa “sin firmar.” Un token con alg: none tiene un segmento de firma vacío.
En 2015, los investigadores mostraron que varias bibliotecas JWT tomaban el campo alg del encabezado como instrucciones para la verificación. Un atacante podía tomar un token HS256 legítimo, cambiar el encabezado a alg: none, eliminar la firma, y la biblioteca aceptaría el token modificado como válido.
La mitigación es verificar contra un algoritmo esperado, no el algoritmo que el token reclama:
// INCORRECTO — confía en el encabezado
jwt.verify(token, key);
// CORRECTO — fija el algoritmo
jwt.verify(token, key, { algorithms: ["RS256"] });Las bibliotecas modernas rechazan none por defecto o requieren una activación explícita. RFC 8725 (Mejores prácticas actuales de JWT) exige el patrón de fijación de algoritmos.
Otros errores comunes
Confiar en el encabezado kid sin validación
El encabezado kid indica al verificador qué clave usar de un conjunto de claves. Si usas ciegamente kid como ruta de archivo o clave de base de datos, un atacante puede suministrar un valor malicioso. Trata siempre kid como una clave de búsqueda opaca en un conjunto conocido, no como una ruta o consulta.
Confusión de algoritmos (RS256 → HS256)
Algunas bibliotecas aceptan la clave pública para la verificación RS256 pero también la aceptan como secreto HMAC para HS256. Un atacante puede cambiar el encabezado de RS256 a HS256, firmar el token con la clave pública RSA (conocida públicamente) como secreto HMAC, y la biblioteca verificará. Fija el algoritmo.
Tokens de acceso de larga duración sin revocación
Los JWT son sin estado — el verificador no habla con el emisor por solicitud. Ese es todo el beneficio de rendimiento, y todo el problema de revocación. No puedes invalidar un token filtrado antes de su exp sin mantener una lista de denegación del lado del servidor.
El patrón estándar: token de acceso de corta duración (5-60 minutos) más token de actualización de larga duración (días a semanas) almacenado del lado del servidor. Revoca el token de actualización cuando el usuario cierra sesión.
Poner secretos en el payload
El payload está codificado en base64url, no cifrado. Cualquiera con el token puede leer cada reclamación. No pongas contraseñas, claves API, datos personales sujetos a regulación estricta, ni nada más que no escribirías en un log del servidor. Si necesitas confidencialidad, usa JWE en su lugar.
Saltarse la validación de audiencia e emisor
Un verificador que comprueba la firma pero no las reclamaciones aud y iss aceptará tokens acuñados para un servicio diferente. En un entorno federado esto es un error crítico. RFC 8725 especifica que ambos deben validarse contra valores esperados.
Almacenar JWT en localStorage
localStorage es legible por cualquier JavaScript en tu origen, lo que significa que cualquier XSS se convierte en un robo completo de token. Prefiere cookies seguras HTTP-only para tokens almacenados en el navegador.
Cuándo JWT es la herramienta equivocada
Los JWT resuelven un problema específico: un verificador necesita confiar en una reclamación sin contactar al emisor. Ese problema existe genuinamente en identidad federada y microservicios. Normalmente no existe en una aplicación web propia donde el cliente y el servidor son operados por el mismo equipo.
Para ese caso, una sesión regular del lado del servidor con una cookie segura HTTP-only es más simple:
- Inmediatamente revocable. Elimina la fila de la sesión, el usuario está desconectado.
- Más pequeña en la red. Un ID de sesión es una cookie de 30 bytes; un JWT puede ser de 500-2000 bytes.
- Modelo de seguridad más simple. Sin errores de confusión de algoritmos, sin
alg: none, sin confusión de secreto en el payload. - Más fácil de escalar de lo que la gente afirma. Una búsqueda en Redis o base de datos por solicitud añade un milisegundo como máximo.
JWT vale su complejidad cuando tienes múltiples servicios que necesitan confiar en la identidad del usuario sin hablar todos con un almacén de sesiones central, o cuando estás emitiendo tokens a terceros — exactamente el papel que juegan los JWT en un flujo de OAuth 2.0 / OpenID Connect. Para una aplicación que habla con su propio backend, la cookie es la respuesta correcta y aburrida.
Una plantilla de verificación segura
// Node.js, biblioteca jsonwebtoken
import jwt from "jsonwebtoken";
function verifyToken(token: string): Payload {
return jwt.verify(token, publicKey, {
algorithms: ["RS256"], // fija el algoritmo
issuer: "https://auth.example.com", // fija el emisor
audience: "https://api.example.com", // fija la audiencia
clockTolerance: 5, // pequeña tolerancia para desfase de reloj
}) as Payload;
}Cinco líneas de opciones, cuatro de las cuales mitigan directamente los ataques anteriores.
La conclusión honesta
Decodificar un JWT es trivial — base64url, dividir, analizar el JSON. Nuestro decodificador en el navegador lo hace sin enviar el token a ningún lado, lo que importa porque los tokens a menudo contienen reclamaciones de identificación que no querrías pegar en un sitio de terceros.
Verificar un JWT también es sencillo, siempre que fijes el algoritmo, valides iss, aud y exp, y nunca confíes en que el encabezado te diga cómo verificar. Elige HS256 si el emisor y el verificador son la misma parte; elige RS256 o ES256 con una biblioteca probada en caso contrario. Y antes de llegar a un JWT, comprueba si una cookie de sesión aburrida serviría.
Frequently asked questions
- ¿Puedo confiar en un JWT porque se decodificó?
- No — decodificar solo analiza en base64url el encabezado y el payload. Cualquiera puede decodificar un JWT; la propiedad de seguridad proviene completamente de verificar la firma contra una clave en la que confías. Siempre llama a verify(token, key), no a decode(token), en rutas de código de producción.
- ¿Está cifrado un JWT?
- Un JWT estándar (técnicamente un JWS) está firmado, no cifrado — el payload es legible para cualquiera que tenga el token. Si necesitas confidencialidad, usa un JWE (Cifrado Web JSON) en su lugar. Nunca pongas contraseñas, claves API u otros secretos en un payload JWT asumiendo que están ocultos.
- ¿Qué es el ataque alg: none?
- Las primeras bibliotecas JWT tomaban el campo alg del encabezado como instrucciones — incluyendo el valor especial 'none', que significa 'sin firma'. Un atacante podía cambiar el algoritmo a none, eliminar el segmento de firma, y presentar un token que la biblioteca aceptaba como válido. Las bibliotecas modernas rechazan 'none' por defecto o requieren activación explícita.
- ¿Los tokens de acceso deben ser de larga o corta duración?
- Corta — minutos a una hora como máximo. Los JWT no se pueden revocar sin infraestructura (una lista de denegación explícita anula la premisa sin estado). Una vida corta del token de acceso combinada con un token de actualización de larga duración almacenado en el servidor es el patrón estándar.
- ¿Cuándo es JWT realmente la elección incorrecta?
- Cuando el cliente es una aplicación web propia y controlas ambos lados. Una sesión regular del lado del servidor con una cookie segura HTTP-only es más simple, inmediatamente revocable, más pequeña en la red e inmune a la mayoría de los errores específicos de JWT — no en 'necesitamos inicio de sesión en nuestra aplicación React'.
- ¿Cuál es la diferencia entre iat y nbf?
- iat (emitido en) es cuándo se creó el token — informativo, no un límite de validez. nbf (no antes de) es el momento más temprano en que el token es válido — los verificadores deben rechazar el token antes de nbf. exp (expiración) es el momento más tardío en que el token es válido.
Related
Published May 31, 2026