Publicado en

JWT: qué es, cómo funciona y cuándo usarlo

Estructura de un JWT dividida en tres partes coloreadas: header, payload y signature, sobre fondo oscuro

HTTP es un protocolo sin estado. Cada petición es independiente: el servidor no recuerda nada de las anteriores. Esto crea un problema concreto: si un usuario inicia sesión y luego pide sus datos de perfil, ¿cómo sabe el servidor que ambas peticiones vienen de la misma persona? La respuesta habitual hoy es JWT.

¿Qué es JWT?

JWT (JSON Web Token) es un estándar abierto (RFC 7519) para transmitir información entre dos partes de forma compacta y verificable. En el contexto de autenticación, el servidor lo emite al hacer login y el cliente lo incluye en cada petición posterior. El servidor puede verificar el token sin consultar ninguna base de datos.

Un JWT es una cadena de texto con tres partes separadas por puntos:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
.eyJzdWIiOiIxMjMiLCJlbWFpbCI6ImFuYUBtYWlsLmNvbSIsImV4cCI6MTczNDY1NDI5MH0
.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

Cada parte es independiente, tiene un propósito concreto y está codificada en base64url, que es reversible: no está cifrada. Cualquiera puede decodificarla. Lo que garantiza la integridad del token no es el cifrado, sino la firma.

Las tres partes

Anatomía de un JWT con las tres partes decodificadas: header con algoritmo y tipo, payload con claims del usuario y expiración, y signature con la función HMACSHA256

Header

Contiene el tipo de token y el algoritmo de firma usado para generar la signature.

{
  "alg": "HS256",
  "typ": "JWT"
}

HS256 significa HMAC con SHA-256, el algoritmo simétrico más habitual. Existen variantes asimétricas como RS256 (RSA) que permiten verificar tokens con una clave pública sin exponer la clave privada.

Payload

Contiene los claims: afirmaciones sobre el usuario y el propio token. Hay claims registrados (estándar), públicos y privados.

{
  "sub":   "123",
  "email": "ana@mail.com",
  "rol":   "admin",
  "iat":   1734567890,
  "exp":   1734654290
}

Los claims estándar más usados son:

  • sub (subject): identificador del usuario, normalmente su id.
  • iat (issued at): marca de tiempo de cuándo se emitió el token.
  • exp (expiration): marca de tiempo de cuándo expira. El servidor rechaza tokens expirados.
  • iss (issuer): quién emitió el token (útil en sistemas con múltiples servicios).

El payload es base64url, no cifrado. No incluyas contraseñas, números de tarjeta ni ningún dato sensible. Si necesitas cifrar el contenido, existen los JWE (JSON Web Encryption), pero es un caso de uso menos común.

Signature

Es la garantía de integridad. Se genera aplicando el algoritmo del header sobre la concatenación del header y el payload codificados, usando una clave secreta que solo conoce el servidor:

HMACSHA256(
  base64url(header) + "." + base64url(payload),
  secret_key
)

Cuando el servidor recibe un token, recalcula la firma con su clave secreta y la compara con la que trae el token. Si coinciden, el token es auténtico y no ha sido modificado. Si alguien cambia cualquier byte del payload, la firma deja de coincidir y el token es rechazado.

El flujo completo

Flujo de JWT en cuatro pasos: login con credenciales, servidor firma el token, cliente lo almacena, y peticiones posteriores verifican el token sin consultar la base de datos

El flujo tiene dos momentos diferenciados. El primero ocurre una sola vez:

POST /api/login HTTP/1.1
{ "email": "ana@mail.com", "password": "••••••" }
 
→ 200 OK
{ "token": "eyJhbGci…SflKxw" }

El segundo ocurre en cada petición a recursos protegidos. El cliente incluye el token en la cabecera Authorization:

GET /api/perfil HTTP/1.1
Authorization: Bearer eyJhbGci…SflKxw

El servidor verifica la firma, comprueba que el token no haya expirado y lee el rol del payload. Todo esto ocurre en memoria, sin una sola consulta a la base de datos. Esa es la principal ventaja de JWT frente a las sesiones tradicionales.

JWT vs sesiones de servidor

Las sesiones tradicionales guardan el estado en el servidor: al hacer login, el servidor crea una sesión en base de datos o en Redis y devuelve solo un identificador (session_id) al cliente. En cada petición el servidor busca esa sesión para saber quién es el usuario.

JWT traslada el estado al cliente: el token lleva toda la información necesaria. El servidor solo necesita verificar la firma.

  • Ventaja de JWT: sin estado en el servidor, fácil de escalar horizontalmente. El mismo token funciona en cualquier instancia del servidor sin sincronización.
  • Desventaja de JWT: no se pueden invalidar antes de que expiren. Si un token es robado o un usuario cierra sesión, el token sigue siendo válido hasta su fecha de expiración.
  • Ventaja de sesiones: se pueden invalidar al instante borrándolas del almacén.

Refresh tokens

Para mitigar el problema de la invalidación, se usan dos tokens: un access token de vida corta (15-60 minutos) y un refresh token de vida larga (días o semanas) que se guarda de forma segura y solo sirve para pedir un nuevo access token.

// Al login
access_token  → expira en 15 min  (va en cada petición)
refresh_token → expira en 7 días  (se guarda en httpOnly cookie)
 
// Cuando el access_token expira
POST /api/refresh
Cookie: refresh_token=eyJ...
 
→ nuevo access_token

Si el usuario cierra sesión, se invalida el refresh token en la base de datos. El access token seguirá siendo válido durante sus 15 minutos de vida, pero el daño potencial es mínimo.

Buenas prácticas

  • Tiempos de expiración cortos: cuanto menos dure un access token, menor es la ventana de exposición si es comprometido.
  • Guarda los tokens en httpOnly cookies: son inaccesibles desde JavaScript, lo que previene ataques XSS. Evita localStorage para tokens sensibles.
  • Usa HTTPS siempre: un token en tránsito sin cifrar es tan vulnerable como una contraseña en texto plano.
  • No pongas datos sensibles en el payload: es legible por cualquiera. Solo incluye lo mínimo necesario (id, rol, expiración).
  • Valida el algoritmo: algunos ataques intentan cambiar el alg del header a none. El servidor debe rechazar tokens con algoritmos inesperados.

Con la autenticación y JWT cubiertos, el siguiente paso es cómo conecta el backend con la base de datos: si escribir SQL directamente, usar un ORM, o algún punto intermedio entre los dos.