CONCEPTOS BASE

JWT

Estándar para representar credenciales de autenticación como un token firmado y autocontenido. Forma habitual de proteger APIs modernas sin sesiones en servidor.

Nivel · intermedio4 min de lecturaActualizado 22 may 2026
También conocido como: JSON Web Token

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:

  1. Usuario hace login con email/password en POST /auth/login
  2. Servidor verifica credenciales, genera un JWT firmado con la clave secreta y lo devuelve
  3. Cliente guarda el JWT (localStorage, cookie httpOnly, secure storage en móvil...)
  4. En cada petición a la API, el cliente envía el header Authorization: Bearer eyJhbGc...
  5. 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:

ClaimSignificado
subSubject — quién es el dueño del token (típicamente user_id)
iatIssued at — cuándo se emitió (timestamp Unix)
expExpiration — cuándo caduca (timestamp Unix)
issIssuer — quién lo emitió (tu API)
audAudience — para quién es válido
nbfNot before — válido a partir de esta fecha
jtiJWT 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: especificar alg: "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 exp es 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_SECRET es "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

Tagsseguridadauthbackendapi
Escrito por
Antonio Echeverría

Dirijo IMDICA, una empresa de suministro industrial, desde 2007. Escribo estas definiciones desde el lado de quien las usa para decidir, no desde el de quien las estudia.

Si necesitas que alguien construya esto de verdad y no solo lo explique, eso es lo que hago en AE Works.