JWT Tokens Explained: Structure, Security & Decoding
Understand JSON Web Tokens from the inside out. Learn the header, payload, and signature structure, common claims, signing algorithms, and security best practices.
¿Qué es un JWT?
Un JSON Web Token (JWT, pronunciado «jot») es una cadena compacta y segura para URL que representa afirmaciones (claims) entre dos partes. Es la forma más habitual de manejar autenticación y autorización en aplicaciones web modernas.
Cuando inicias sesión en una aplicación web y el servidor te devuelve un token en lugar de crear una sesión del lado del servidor, lo más probable es que ese token sea un JWT. Tu navegador lo envía en cada solicitud posterior, y el servidor verifica la firma sin consultar una base de datos.
Dónde se usan los JWT
- Autenticación. Tras iniciar sesión, el servidor emite un JWT. El cliente lo almacena y lo envía en el encabezado
Authorizationen cada solicitud. - Inicio de sesión único (SSO). Los JWT permiten que los usuarios se autentiquen una vez y accedan a varios servicios. Proveedores de identidad como Auth0, Okta y Keycloak emiten JWT que los servicios posteriores pueden verificar de forma independiente.
- Autorización de API. Los microservicios intercambian JWT entre sí para demostrar que una solicitud se hizo en nombre de un usuario autenticado con permisos concretos.
- Intercambio de información. Al estar firmados, los JWT pueden transportar datos confiables entre partes sin un paso adicional de verificación.
Las tres partes de un JWT
Todo JWT consta de tres partes codificadas en Base64URL separadas por puntos:
header.payload.signature
Aquí hay un JWT real (acortado para facilitar la lectura):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Decodifiquemos cada parte.
Parte 1: Encabezado
El encabezado describe el tipo de token y el algoritmo de firma. Decodificado desde Base64URL:
{
"alg": "HS256",
"typ": "JWT"
}
alg indica al verificador qué algoritmo usar al comprobar la firma. typ confirma que es un JWT (en oposición a un JWE u otro tipo de token).
Parte 2: Carga útil (claims)
La carga útil contiene los claims: los datos reales que transporta el token. Decodificada:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
Los claims son pares clave-valor. Algunos están estandarizados (registered claims) y puedes añadir los claims personalizados que necesites.
La carga útil está codificada en Base64URL, no cifrada. Cualquiera que tenga el token puede decodificarla y leer la carga útil. Nunca pongas secretos, contraseñas ni datos personales sensibles en el payload de un JWT.
Parte 3: Firma
La firma se calcula tomando el encabezado codificado, un punto, la carga útil codificada, y firmándolo con una clave secreta:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
La firma garantiza que el token no ha sido alterado. Si alguien cambia un solo carácter en el encabezado o en la carga útil, la firma no coincidirá y el verificador rechazará el token.
Pruébalo tú mismo: Pega cualquier JWT en nuestro decodificador JWT para inspeccionar las tres partes al instante.
Claims comunes en JWT
La especificación JWT (RFC 7519) define un conjunto de registered claims. Ninguno es obligatorio, pero están muy extendidos:
| Claim | Nombre completo | Descripción |
|---|---|---|
sub | Sujeto | Sobre quién trata el token (normalmente un ID de usuario) |
iss | Emisor | Quién emitió el token (p. ej., la URL de tu servidor de autenticación) |
aud | Audiencia | Destinatario previsto (p. ej., la URL de tu API) |
exp | Fecha de expiración | Marca de tiempo Unix tras la cual el token no es válido |
iat | Emitido en | Marca de tiempo Unix en que se creó el token |
nbf | No válido antes de | El token no es válido antes de esta marca de tiempo Unix |
jti | ID del JWT | Identificador único para evitar el reuso del token |
Los claims personalizados son cualquier otro que añadas. Ejemplos habituales: role, permissions, email y org_id. Mantén los claims personalizados al mínimo: cada byte extra se envía en cada solicitud.
Algoritmos de firma comparados
La elección del algoritmo determina cómo se crea y verifica la firma. Estas son las tres opciones más comunes:
| Algoritmo | Tipo | Clave | Ideal para |
|---|---|---|---|
HS256 | Simétrico (HMAC) | Un único secreto compartido | Configuraciones sencillas donde emisor y verificador son el mismo servicio |
RS256 | Asimétrico (RSA) | La clave privada firma, la pública verifica | Sistemas distribuidos, SSO: los verificadores nunca necesitan la clave privada |
ES256 | Asimétrico (ECDSA) | La clave privada firma, la pública verifica | Igual que RS256 pero con claves más pequeñas y operaciones más rápidas |
Simétrico (HS256) es más sencillo de configurar: emisor y verificador comparten el mismo secreto. La desventaja es que todo servicio que deba verificar tokens debe tener acceso al secreto, lo que amplía la superficie de ataque.
Asimétrico (RS256, ES256) encaja mejor en arquitecturas distribuidas. El servidor de autenticación firma con una clave privada y cualquier servicio puede verificar con la clave pública correspondiente, que es seguro distribuir. ES256 es la opción moderna: seguridad equivalente a RS256 con tokens más pequeños y verificación más rápida.
Buenas prácticas de seguridad
Los JWT son una herramienta potente, pero es fácil usarlos mal. Sigue estas reglas:
- Nunca guardes secretos en la carga útil. La carga útil está codificada, no cifrada. Cualquiera puede decodificarla. Si necesitas tokens cifrados, usa JWE (JSON Web Encryption).
- Usa tiempos de expiración cortos. Los access tokens deberían caducar en 5–15 minutos. Usa refresh tokens para sesiones más largas. Los tokens de vida corta limitan el daño si uno es robado.
- Usa siempre HTTPS. Los JWT enviados por HTTP pueden interceptarse en tránsito. HTTPS no es negociable.
- Valida todos los claims. Comprueba siempre
exp,issyaud. No basta con verificar la firma: un token expirado o mal dirigido debe rechazarse. - No aceptes
alg: none. Algunas bibliotecas aceptan tokens sin firmar si el encabezado dice"alg": "none". Rechaza explícitamente este algoritmo en la configuración del verificador. - Usa una lista permitida de algoritmos. Configura el verificador para aceptar solo el o los algoritmos que esperas (p. ej., solo RS256). Esto previene ataques de confusión de algoritmo.
- Almacena los tokens de forma segura. Las cookies
HttpOnlyson más seguras quelocalStorageporque no son accesibles desde JavaScript (mitigan ataques XSS). Si usaslocalStorage, entiende los compromisos.
Errores frecuentes con JWT
Son patrones que aparecen una y otra vez en auditorías de seguridad:
- Poner el JWT en la URL. Las cadenas de consulta acaban en registros del servidor, historial del navegador y encabezados referrer. Usa el encabezado
Authorizationo una cookie. - No validar la firma en absoluto. Algunos desarrolladores decodifican la carga útil con una biblioteca Base64 y omiten la verificación. Eso permite que un atacante falsifique cualquier token.
- Usar un secreto débil para HS256. Si el secreto compartido es
"secret"o"password123", puede forzarse por fuerza bruta en segundos. Usa una cadena criptográficamente aleatoria de al menos 256 bits. - Tokens que nunca expiran. Si un token no tiene claim
exp, es válido para siempre. Si ese token se filtra, no hay forma de revocarlo sin cambiar la clave de firma (lo que invalida todos los tokens). - Guardar demasiados datos. Los JWT se envían en cada solicitud HTTP. Un token de 4 KB añade sobrecarga a cada llamada a la API. Mantén la carga útil ligera.
Flujo de renovación de tokens
Los access tokens de vida corta caducan pronto, así que necesitas una forma de obtener otros sin obligar al usuario a iniciar sesión de nuevo. Ese es el flujo del refresh token:
- El usuario inicia sesión. El servidor devuelve un access token (de vida corta, p. ej., 15 minutos) y un refresh token (de vida larga, p. ej., 7 días).
- El cliente envía el access token en cada solicitud a la API en el encabezado
Authorization: Bearer. - Cuando el access token expira, la API responde con
401 Unauthorized. - El cliente envía el refresh token a un endpoint dedicado
/refresh. - El servidor valida el refresh token, emite un nuevo access token (y opcionalmente un nuevo refresh token: esto se llama rotación de refresh tokens) y los devuelve.
- El cliente reintenta la solicitud original con el nuevo access token.
La rotación de refresh tokens es una buena práctica de seguridad: cada refresh token solo puede usarse una vez. Si un atacante roba un refresh token y el usuario legítimo también intenta usarlo, el servidor detecta el reuso e invalida toda la sesión.
// Simplified refresh flow (client-side)
const handleApiRequest = async (url, options) => {
let response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${getAccessToken()}`,
},
});
if (response.status === 401) {
const refreshResponse = await fetch("/api/refresh", {
method: "POST",
body: JSON.stringify({ refresh_token: getRefreshToken() }),
});
if (refreshResponse.ok) {
const { access_token, refresh_token } = await refreshResponse.json();
saveTokens(access_token, refresh_token);
response = await fetch(url, {
...options,
headers: {
...options.headers,
Authorization: `Bearer ${access_token}`,
},
});
} else {
redirectToLogin();
}
}
return response;
};
JWT frente a sesiones: cuándo usar cada uno
Es uno de los temas más debatidos en el desarrollo web. Ambos enfoques tienen usos legítimos:
| Aspecto | JWT | Sesiones del lado del servidor |
|---|---|---|
| Almacenamiento de estado | Cliente (servidor sin estado) | Servidor (almacén de sesiones / base de datos) |
| Escalabilidad | Sencilla: no hay estado compartido entre servidores | Requiere almacén de sesiones compartido (Redis, BD) |
| Revocación | Difícil: hace falta lista de bloqueo o expiración corta | Sencilla: borrar la sesión del almacén |
| Tamaño de la carga | Mayor (lleva claims en cada solicitud) | Menor (solo un ID de sesión en la cookie) |
| Entre dominios | Funciona bien (enviado como Bearer) | Requiere configuración de cookies y CORS |
| Microservicios | Ideal: cada servicio verifica de forma independiente | Cada servicio debe consultar el almacén de sesiones |
| Simplicidad | Más piezas (firma, flujo de refresh) | Más sencillo implementarlo bien |
Usa JWT cuando necesites autenticación sin estado entre servicios distribuidos, apps móviles o consumidores de API de terceros.
Usa sesiones cuando necesites revocación sencilla (p. ej., «cerrar sesión en todos los dispositivos»), ejecutes una sola app monolítica o quieras la opción segura más simple para una aplicación web tradicional.
Pruébalo tú mismo: Decodifica e inspecciona cualquier JWT al instante con nuestro decodificador JWT: los datos no salen de tu navegador.