Introducción: ¿Qué es un JSON Web Token?
JSON Web Token (JWT) es un estándar abierto (RFC 7519) que define una forma compacta y autónoma de transmitir información de forma segura entre partes como un objeto JSON. Esta información puede ser verificada y considerada de confianza porque está firmada digitalmente. Los JWT pueden firmarse con un secreto (algoritmo HMAC) o con un par de claves pública/privada (RSA o ECDSA).
Los JWT nacieron para resolver un reto frecuente en los sistemas distribuidos: ¿cómo puede un servidor confiar en una solicitud de un cliente sin necesidad de consultar una sesión en la base de datos? La respuesta es codificar y firmar la identidad y los permisos del usuario directamente en un token que el cliente adjunta a cada petición.
Hoy en día, los JWT son la columna vertebral de la autenticación y autorización modernas:
- Single Sign-On (SSO): Un único inicio de sesión concede acceso a múltiples servicios.
- Autenticación de API: Las apps móviles y las SPA autentican las llamadas a la API con un Bearer token.
- OAuth 2.0 / OpenID Connect: Los JWT se usan como access tokens e ID tokens.
- Microservicios: Los servicios verifican la identidad del llamante sin un almacén central de sesiones.
Estructura del JWT: header.payload.signature
Un JWT es una cadena de tres partes codificadas en Base64url, separadas por puntos (.):
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
Parte 1 — Header (Cabecera) decodificado:
{
"alg": "HS256",
"typ": "JWT"
}
El header especifica el algoritmo de firma (alg) y el tipo de token (typ).
Parte 2 — Payload (Carga útil) decodificado:
{
"sub": "1234567890",
"name": "John Doe",
"iat": 1516239022
}
El payload contiene los claims — declaraciones sobre la entidad (normalmente un usuario) y metadatos adicionales.
Parte 3 — Signature (Firma) (para HS256) se calcula así:
HMACSHA256(
base64UrlEncode(header) + "." + base64UrlEncode(payload),
secret
)
La firma garantiza que el token no ha sido manipulado. Nota crítica: El payload solo está codificado en Base64url, no cifrado. Cualquiera que obtenga el token puede leer todos los claims.
Algoritmos de Firma: HS256, RS256 y ES256
HS256 (HMAC-SHA256) — Simétrico
Utiliza un único secreto compartido tanto para firmar como para verificar. Simple de implementar, pero el mismo secreto debe ser conocido por el emisor y el verificador.
- Ideal para: Aplicaciones monolíticas o servicios fuertemente acoplados que comparten un secreto.
- Riesgo: Si el secreto se filtra, todos los tokens pueden ser falsificados.
RS256 (RSA-SHA256) — Asimétrico
Usa una clave privada para firmar y una clave pública para verificar. La clave privada permanece secreta; la pública puede compartirse libremente a través de un endpoint JWKS.
- Ideal para: Sistemas distribuidos, microservicios y escenarios donde múltiples servicios verifican tokens.
- Tamaño de clave: Se recomienda un mínimo de 2048 bits para RSA.
ES256 (ECDSA-SHA256) — Asimétrico
Utiliza el Algoritmo de Firma Digital de Curva Elíptica con la curva P-256. Produce firmas más pequeñas que RS256 con seguridad equivalente.
- Ideal para: Entornos con restricciones de rendimiento o ancho de banda.
| Algoritmo | Tipo | Clave | Tamaño de firma | Caso de uso |
|---|---|---|---|---|
| HS256 | Simétrico | Secreto compartido | 32 bytes | Apps de un solo servicio |
| RS256 | Asimétrico | Par de claves RSA | 256+ bytes | Sistemas distribuidos |
| ES256 | Asimétrico | Par de claves EC | 64 bytes | APIs de alto rendimiento |
Claims de JWT: Registrados, Públicos y Privados
Los claims son declaraciones sobre una entidad (normalmente un usuario) y metadatos codificados en el payload.
Claims Registrados (definidos por IANA)
| Claim | Nombre completo | Descripción |
|---|---|---|
iss |
Issuer | Quién emitió el token (ej: "auth.example.com") |
sub |
Subject | A quién se refiere el token (ej: un ID de usuario) |
aud |
Audience | Para quién está destinado el token |
exp |
Expiration | Marca de tiempo Unix tras la cual el token es inválido |
nbf |
Not Before | El token no debe usarse antes de esta marca de tiempo |
iat |
Issued At | Marca de tiempo Unix de cuando se emitió el token |
jti |
JWT ID | Identificador único para prevenir ataques de repetición |
Claims Públicos
Definidos en el Registro de Claims JWT de IANA. Ejemplos: email, name, picture, roles.
Claims Privados
Claims personalizados acordados entre emisor y consumidor. Usa nombres con espacio de nombres para evitar colisiones:
{
"https://example.com/tenant_id": "acme-corp",
"https://example.com/roles": ["admin", "editor"]
}
Cómo Funciona el Flujo de Autenticación con JWT
- Inicio de sesión: El cliente envía credenciales (usuario + contraseña) al servidor de autenticación.
- Emisión del token: El servidor valida las credenciales, crea un JWT firmado con los claims apropiados y lo devuelve al cliente.
- Almacenamiento: El cliente guarda el JWT (en memoria, localStorage o una cookie HttpOnly).
- Solicitudes autenticadas: Para cada petición a un recurso protegido, el cliente incluye el JWT en la cabecera
Authorization:Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9... - Verificación del token: El servidor extrae el token, verifica la firma, comprueba el
expy concede o deniega el acceso según los claims. - Renovación del token: Cuando el access token expira, el cliente usa un refresh token de larga duración para obtener uno nuevo sin volver a autenticarse.
Cliente Servidor Auth Servidor de Recursos
| | |
|-- POST /login ---->| |
|<-- 200 + JWT ------| |
| | |
|-- GET /api (Authorization: Bearer JWT) ->|
| | verificar JWT |
|<----------- 200 + datos ---------------->|
Consideraciones de Seguridad
El Payload NO Está Cifrado
La codificación Base64url no es cifrado. Cualquiera que tenga un JWT puede decodificar y leer todos los claims al instante. Nunca incluyas en el payload de un JWT:
- Contraseñas o hashes de contraseñas
- Números de tarjetas de crédito u otros datos financieros
- Información personal sensible (PII) más allá de lo necesario
- Claves privadas o secretos internos
Siempre Verifica la Firma
// Node.js — biblioteca jsonwebtoken
const jwt = require("jsonwebtoken");
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
console.log(decoded.sub); // ID de usuario confiable
} catch (err) {
res.status(401).json({ error: "Token inválido o expirado" });
}
Usa Tiempos de Expiración Cortos
Los access tokens deberían tener ciclos de vida cortos (15 minutos a 1 hora). Usa refresh tokens para sesiones largas y limitar el daño si un token es robado.
Usa Siempre HTTPS
Nunca transmitas tokens sobre conexiones no cifradas. Un token interceptado es tan peligroso como una contraseña robada.
Vulnerabilidades Comunes
1. Ataque de Confusión de Algoritmos (CVE-2015-9235)
Un atacante cambia el campo alg en el header de RS256 a HS256 y firma el token usando la clave pública del servidor como secreto HMAC. Un servidor mal implementado acepta el token falsificado.
Mitigación: Especifica siempre el algoritmo esperado de forma explícita en el servidor:
jwt.verify(token, publicKey, { algorithms: ["RS256"] });
2. El Ataque del Algoritmo "none"
Si un servidor acepta alg: "none", un atacante puede crear tokens sin ninguna firma.
Mitigación: Rechaza explícitamente los tokens con alg: "none". Las bibliotecas modernas lo corrigen, pero las versiones antiguas pueden ser vulnerables.
3. Secretos Débiles (HS256)
Un secreto HMAC corto o fácil de adivinar puede ser vulnerado mediante fuerza bruta offline con herramientas como hashcat contra un token capturado.
Mitigación: Usa un secreto aleatorio criptográficamente seguro de al menos 256 bits:
openssl rand -hex 32
4. Validación de Claims Ausente
No validar iss, aud o nbf permite la reutilización de tokens entre servicios o su uso antes del período válido.
5. Almacenamiento Inseguro de Tokens
localStorage es accesible por cualquier JavaScript de la página, exponiendo los tokens a ataques XSS. Prefiere las cookies HttpOnly para tokens sensibles.
JWT vs Tokens de Sesión: Sin Estado vs Con Estado
| Característica | JWT (Sin estado) | Token de sesión (Con estado) |
|---|---|---|
| Almacenamiento | Lado del cliente | Lado del servidor (BD o caché) |
| Escalabilidad | Excelente — sin consultas al servidor | Requiere almacén de sesiones compartido |
| Revocación | Difícil — válido hasta exp |
Fácil — eliminar de la base de datos |
| Tamaño del token | Mayor en cada petición | Token pequeño, datos en el servidor |
| Microservicios | Ideal — verificación independiente | Requiere infraestructura compartida |
| Visibilidad | Cualquiera con el token puede leerlo | Los datos solo están en el servidor |
Elige JWT cuando construyas sistemas distribuidos, APIs REST sin estado o apps móviles. Elige sesiones cuando necesites revocación inmediata (ej: "cerrar sesión en todos los dispositivos").
El Desafío de la Revocación
Como los JWT son autónomos, no puedes cancelar un token antes de su expiración sin infraestructura adicional:
- Expiración corta + refresh tokens: Los access tokens caducan rápido; revoca los refresh tokens en la BD.
- Lista negra de tokens: Almacena los
jtirevocados en Redis (reintroduce algo de estado). - Rotación de secretos: Rota el secreto de firma para invalidar todos los tokens (afecta a todos los usuarios).
OAuth 2.0 y OpenID Connect
OAuth 2.0
OAuth 2.0 es un framework de autorización que no exige un formato de token específico. Sin embargo, los JWT se usan ampliamente como access tokens de OAuth porque son autónomos y pueden llevar scopes de autorización:
{
"iss": "https://auth.example.com",
"sub": "user_123",
"aud": "https://api.example.com",
"scope": "read:profile write:posts",
"exp": 1893456000
}
OpenID Connect (OIDC)
OpenID Connect es una capa de identidad construida sobre OAuth 2.0. Introduce el ID Token, que siempre es un JWT y contiene claims de identidad sobre el usuario autenticado:
{
"iss": "https://accounts.google.com",
"sub": "110169484474386276334",
"aud": "my-client-id.apps.googleusercontent.com",
"email": "[email protected]",
"name": "Jane Doe",
"exp": 1893456000,
"iat": 1893452400
}
OIDC también define el endpoint JWKS (/.well-known/jwks.json), donde el servidor de autorización publica sus claves públicas. Cualquier servicio puede obtener estas claves para verificar tokens sin un intercambio de claves previo.
Buenas Prácticas
- Usa algoritmos asimétricos (RS256/ES256) para sistemas públicos — protege la clave privada de firma mientras permites que cualquiera verifique con la clave pública.
- Establece valores de
expcortos para access tokens — 15 minutos es un valor predeterminado seguro y habitual. - Rota siempre los refresh tokens — emite uno nuevo cada vez que se usa e invalida el anterior.
- Valida todos los claims relevantes — comprueba
iss,aud,expynbfcomo mínimo. - Especifica el algoritmo esperado de forma explícita — nunca confíes solo en el campo
algdel header del token. - Nunca pongas datos sensibles en el payload — está codificado, no cifrado.
- Usa siempre HTTPS — no transmitas tokens sobre conexiones no cifradas.
- Almacena los tokens de forma segura — prefiere cookies
HttpOnly,Secure,SameSite=Strictfrente a localStorage. - Implementa una estrategia de revocación de tokens — usa lista negra o tokens de corta duración con rotación de refresh tokens.
- Mantén las bibliotecas JWT actualizadas — las versiones nuevas incluyen parches de seguridad.
Preguntas Frecuentes (FAQ)
P: ¿Puedo descifrar el payload de un JWT? R: El payload de un JWT está codificado en Base64url, no cifrado — cualquiera puede leerlo sin ninguna clave. Si necesitas confidencialidad, usa JWE (JSON Web Encryption, RFC 7516), que sí cifra el payload.
P: ¿Cuál es la diferencia entre decode y verify?
R: decode simplemente decodifica las partes en Base64url y devuelve el JSON sin comprobaciones de seguridad. verify además valida la firma con tu clave y comprueba los claims temporales como exp. Usa siempre verify en producción.
P: ¿Cómo gestiono la renovación de tokens de forma segura? R: Emite un access token de corta duración (15–60 min) y un refresh token de larga duración (días o semanas). Almacena los refresh tokens en el servidor para poder revocarlos. Cuando el access token expira, el cliente envía el refresh token a un endpoint dedicado para obtener uno nuevo.
P: ¿Puedo usar JWT tanto para autenticación como para autorización?
R: Sí. Incluye claims de identidad (quién es el usuario) y claims de autorización (qué puede hacer, ej: roles, scope) en el mismo token. Mantén el tamaño del payload razonable.
P: ¿Qué hago si se filtra mi secreto JWT? R: Un atacante puede falsificar tokens válidos para cualquier usuario. Rota el secreto inmediatamente (esto invalida todos los tokens existentes), investiga la fuente de la filtración y obliga a todos los usuarios a volver a autenticarse.
P: ¿Debo usar HS256 o RS256? R: Usa RS256 (o ES256) cuando múltiples servicios verifican tokens o cuando el verificador no es de plena confianza. Usa HS256 solo cuando el mismo servicio emite y verifica tokens dentro de un límite de despliegue controlado.
P: ¿Qué tan grande puede ser un JWT? R: Mantén los JWT por debajo de 4 KB para evitar límites de tamaño de cabeceras HTTP y problemas de rendimiento. No incrusta estructuras de datos grandes; referéncialas por ID.