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