jwt decode security authentication tokens

Entendiendo los JWT: Cómo Decodificar y Generar JSON Web Tokens

Una guía completa sobre JSON Web Tokens (JWT). Aprende a decodificar, verificar y generar tokens para la depuración de autenticación segura.

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

  1. Inicio de sesión: El cliente envía credenciales (usuario + contraseña) al servidor de autenticación.
  2. Emisión del token: El servidor valida las credenciales, crea un JWT firmado con los claims apropiados y lo devuelve al cliente.
  3. Almacenamiento: El cliente guarda el JWT (en memoria, localStorage o una cookie HttpOnly).
  4. Solicitudes autenticadas: Para cada petición a un recurso protegido, el cliente incluye el JWT en la cabecera Authorization:
    Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
    
  5. Verificación del token: El servidor extrae el token, verifica la firma, comprueba el exp y concede o deniega el acceso según los claims.
  6. 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 jti revocados 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

  1. 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.
  2. Establece valores de exp cortos para access tokens — 15 minutos es un valor predeterminado seguro y habitual.
  3. Rota siempre los refresh tokens — emite uno nuevo cada vez que se usa e invalida el anterior.
  4. Valida todos los claims relevantes — comprueba iss, aud, exp y nbf como mínimo.
  5. Especifica el algoritmo esperado de forma explícita — nunca confíes solo en el campo alg del header del token.
  6. Nunca pongas datos sensibles en el payload — está codificado, no cifrado.
  7. Usa siempre HTTPS — no transmitas tokens sobre conexiones no cifradas.
  8. Almacena los tokens de forma segura — prefiere cookies HttpOnly, Secure, SameSite=Strict frente a localStorage.
  9. Implementa una estrategia de revocación de tokens — usa lista negra o tokens de corta duración con rotación de refresh tokens.
  10. 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.