Saltar al contenido
JSONCraft

Seguridad

Validación JWT: ejemplo ejecutable y comprobaciones de rechazo

Verifica un token sintético con una biblioteca concreta, rechaza audiencia y caducidad incorrectas y distingue decodificar de autenticar.

Un JWT firmado habitual tiene tres segmentos base64url: cabecera, payload y firma. Su contenido legible no está cifrado. JWT también admite representaciones cifradas; la descripción de tres segmentos no se aplica a todos los casos.

Reproduce la verificación con claves desechables

Crea un directorio temporal, descarga jwt-validation.mjs e instala la biblioteca utilizada:

npm install --save-exact jose@6.1.3
node jwt-validation.mjs

El ejemplo se comprobó con Node 22.23.2. La instalación descarga un paquete; el script no hace peticiones de red. Genera claves ES256 temporales y un usuario ficticio, y ejecuta un caso válido y dos rechazos. No sustituyas estos datos por tokens o claves privadas de producción.

import { generateKeyPair, SignJWT, jwtVerify, decodeJwt } from 'jose';
import assert from 'node:assert/strict';

// Disposable demonstration keys and claims. Never a production key or live token.
const { publicKey, privateKey } = await generateKeyPair('ES256');
const token = await new SignJWT({ role: 'reader' })
  .setProtectedHeader({ alg: 'ES256', typ: 'JWT' })
  .setSubject('demo-user').setIssuer('https://issuer.example')
  .setAudience('https://api.example').setIssuedAt().setExpirationTime('2m')
  .sign(privateKey);
const options = {
  algorithms: ['ES256'], issuer: 'https://issuer.example', audience: 'https://api.example',
  typ: 'JWT', requiredClaims: ['sub', 'iat', 'exp'], clockTolerance: 5
};
const { payload } = await jwtVerify(token, publicKey, options);
assert.equal(payload.sub, 'demo-user');
assert.equal(decodeJwt(token).sub, 'demo-user'); // decoding alone establishes no trust
await assert.rejects(jwtVerify(token, publicKey, { ...options, audience: 'https://other.example' }));
await assert.rejects(jwtVerify(token, publicKey, { ...options, currentDate: new Date(Date.now() + 300000) }));
console.log('Valid token accepted; wrong audience and expired token rejected');

Salida esperada: Valid token accepted; wrong audience and expired token rejected. Decodificar el token también devuelve demo-user, pero esa lectura no demuestra autenticidad. Un token modificado o sin firma no debe considerarse fiable porque puedas leer su contenido.

Define el contrato del verificador

Vincula las claves a un emisor esperado y a algoritmos permitidos. Exige los campos que necesita tu protocolo y comprueba la audiencia. Este ejemplo exige exp; la especificación base no lo convierte en obligatorio para cualquier uso imaginable de JWT. La tolerancia de reloj debe corresponder al comportamiento medido, no ampliar silenciosamente la validez durante mucho tiempo.

Para JWKS remoto, usa el endpoint documentado del emisor de confianza y limita peticiones y caché. No descargues URLs arbitrarias indicadas por un token sin verificar. Distingue access tokens, ID tokens y otros objetos firmados: una firma válida no los vuelve intercambiables entre endpoints.

Elige por compatibilidad y reparto de confianza

ES256 es una opción compatible con el ejemplo, no un ganador universal. RSA, curvas elípticas y HMAC tienen distintas restricciones de interoperabilidad, custodia de claves y políticas. Con HS256, quien verifica con el secreto compartido también puede crear tokens. La verificación asimétrica permite comprobar sin entregar la clave de firma. No elijas a partir de una cifra de velocidad sin una prueba relevante.

Caducidad, revocación y almacenamiento

Una caducidad corta limita el tiempo de uso de un access token robado, pero no lo invalida inmediatamente. La revocación inmediata necesita un registro de sesión/versión, una lista de rechazo u otro mecanismo del protocolo. Revocar un refresh token suele impedir renovar, sin invalidar todos los access tokens ya emitidos.

Las cookies HttpOnly impiden leer el token directamente con JavaScript, pero la autenticación por cookies sigue necesitando defensas CSRF. Guardarlo sólo en memoria evita una copia persistente, sin neutralizar XSS. La elección depende de la aplicación; ambas requieren prevenir código inyectado.

Para inspeccionar, decodifica segmentos de un token sintético localmente. Base64 y Hash de JSONCraft no son un verificador de firma y claims JWT. Usa una biblioteca de autenticación y comprueba también los casos que debe rechazar.

Referencias: JWT RFC 7519, buenas prácticas JWT y API jwtVerify de jose.

Herramientas relacionadas

Preguntas frecuentes

¿Decodificar un JWT comprueba la firma?

No. Lee afirmaciones aportadas por el emisor. Confiar en ellas requiere verificación criptográfica y las comprobaciones de tu aplicación.

¿Un token en memoria está protegido frente a XSS?

No. El código ejecutado en la página puede actuar con la autoridad del usuario y acceder o interceptar tokens. Elegir almacenamiento sólo cubre una parte del riesgo.

Por