Definición
JWT (siglas de JSON Web Token, pronunciado "jot") es un estándar abierto (RFC 7519) para representar credenciales de autenticación como un token autocontenido y firmado criptográficamente. Se ha convertido en la forma más habitual de autenticar APIs modernas, especialmente las que sirven a apps móviles y SPAs.
La idea clave: en lugar de mantener sesiones en el servidor (cookies con un session ID que busca en BD), el servidor emite un token que contiene toda la información del usuario dentro y va firmado para que no pueda ser falsificado. El cliente lo guarda y lo envía en cada petición; el servidor solo verifica la firma — no necesita consultar nada.
Esto hace que las APIs con JWT escalen sin esfuerzo: no hay estado de sesión, así que cualquier servidor del cluster puede atender cualquier petición sin necesidad de Redis ni sticky sessions.
Estructura
Un JWT son 3 partes separadas por puntos: header.payload.signature
eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiIxMjMiLCJuYW1lIjoiQW50b25pbyJ9.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
│ │ │
└─── HEADER ──┘ └────── PAYLOAD ─────┘ └────────── SIGNATURE ──────┘
Cada parte es JSON codificado en Base64URL:
Header — qué algoritmo se usa para firmar:
{ "alg": "HS256", "typ": "JWT" }
Payload — los datos del usuario (llamados "claims"):
{
"sub": "user_12345",
"name": "Antonio Echeverría",
"role": "admin",
"iat": 1716369600,
"exp": 1716456000
}
Signature — firma del header+payload con una clave secreta:
HMACSHA256(base64(header) + "." + base64(payload), CLAVE_SECRETA)
Si alguien modifica el payload, la firma deja de coincidir y el token es inválido.
Cómo se usa en una API
Flujo típico:
- Usuario hace login con email/password en
POST /auth/login - Servidor verifica credenciales, genera un JWT firmado con la clave secreta y lo devuelve
- Cliente guarda el JWT (localStorage, cookie httpOnly, secure storage en móvil...)
- En cada petición a la API, el cliente envía el header
Authorization: Bearer eyJhbGc... - Servidor verifica la firma con la misma clave secreta. Si es válida y no ha caducado, atiende la petición usando los datos del payload (user_id, role, etc.)
Ejemplo práctico
Backend Node.js firmando y verificando un JWT:
import jwt from 'jsonwebtoken';
const SECRET = process.env.JWT_SECRET; // mínimo 256 bits
// 1. Al hacer login, firmar token
function login(usuario) {
return jwt.sign(
{ sub: usuario.id, role: usuario.role },
SECRET,
{ expiresIn: '1h' }
);
// → "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
// 2. Middleware que protege endpoints
function requireAuth(req, res, next) {
const header = req.headers.authorization;
if (!header?.startsWith('Bearer ')) {
return res.status(401).json({ error: 'No token' });
}
const token = header.slice(7);
try {
const payload = jwt.verify(token, SECRET);
req.user = payload;
next();
} catch (e) {
return res.status(401).json({ error: 'Invalid token' });
}
}
app.get('/api/private', requireAuth, (req, res) => {
res.json({ message: `Hola ${req.user.sub}` });
});
Claims estándar
JWT define varios claims con significado fijo:
| Claim | Significado |
|---|---|
sub | Subject — quién es el dueño del token (típicamente user_id) |
iat | Issued at — cuándo se emitió (timestamp Unix) |
exp | Expiration — cuándo caduca (timestamp Unix) |
iss | Issuer — quién lo emitió (tu API) |
aud | Audience — para quién es válido |
nbf | Not before — válido a partir de esta fecha |
jti | JWT ID — identificador único (útil para revocar) |
Tú puedes añadir cualquier claim custom (role, email, permissions...), pero NO metas datos sensibles: el payload es solo Base64, cualquiera puede decodificarlo.
Errores comunes
- Confundir "firmado" con "cifrado": el JWT NO está cifrado, solo firmado. Cualquiera puede ver el contenido del payload. No metas contraseñas ni datos médicos.
- Usar el algoritmo
none: especificaralg: "none"desactiva la firma. Hubo CVEs históricas por esto. Siempre exige un algoritmo específico al verificar. - Tokens sin expiración: un token sin
expes válido para siempre. Si lo roban, problema eterno. Pon expiración corta (15 min - 1h) y usa refresh tokens. - Guardar JWT en localStorage sin pensar XSS: localStorage es accesible por cualquier JS de la página. Si tu web tiene una vulnerabilidad XSS, los atacantes roban todos los JWTs. Considera cookies
httpOnly Secure SameSite=Lax. - No saber cómo revocar tokens: la naturaleza autocontenida de JWT hace que sea difícil "deslogear" antes de la expiración. Soluciones: lista de bloqueo (blacklist), tokens cortos + refresh, o rotación.
- Secrets débiles: si tu
JWT_SECRETes "1234" o se filtra al repo, alguien puede forjar tokens. Genera secrets aleatorios largos (256 bits mínimo) y rotalos periódicamente.
Cuándo usar JWT (y cuándo no)
JWT brilla cuando:
- APIs REST/GraphQL stateless con apps móviles o SPA
- Necesitas escalar horizontalmente sin sticky sessions
- Comunicación entre microservicios (token de servicio)
- SSO (Single Sign-On) entre múltiples dominios
Mejor sesiones tradicionales cuando:
- App monolítica web (cookies de sesión funcionan perfectamente)
- Necesitas revocar acceso instantáneamente sin esperar expiración
- Tu equipo no tiene experiencia y los pies se pueden enredar con seguridad
Referencias
- jwt.io — decoder y debugger oficial
- RFC 7519 — especificación
- Auth0 — JWT Handbook — referencia de seguridad