Menu

JWT en Node.js: firmar, verificar y proteger rutas Express

Un JWT es un token firmado de tres partes en base64url que el servidor emite en el login y comprueba en cada petición. En Node.js lo firmas y verificas con el paquete jose o jsonwebtoken y una clave secreta.

Esta página incluye editores ejecutables: edita, ejecuta y ve el resultado al instante.

Un JWT (JSON Web Token) es un string de tres partes en base64url, header.payload.signature, que el servidor le da al usuario en el login y comprueba en cada petición posterior. La firma demuestra que el servidor emitió el token y que nadie lo ha cambiado, pero el payload solo está codificado, no cifrado, así que cualquiera puede leerlo. En Node.js creas uno con new SignJWT(payload).sign(secret) del paquete jose y lo compruebas con jwtVerify(token, secret).

Imprime 3 parts y luego user-42 admin: jwtVerify comprobó la firma y la expiración y devolvió el payload. Cambia un carácter del secreto que se pasa a jwtVerify y vuelve a ejecutarlo: la verificación falla con signature verification failed.

Qué hay dentro de un JWT

El header y el payload son JSON codificado en base64url. Decodificarlos no requiere ninguna clave, y por eso un JWT nunca debe contener una contraseña ni nada secreto.

Las dos primeras líneas imprimen {"alg":"HS256"} y {"name":"Ada","role":"editor"}, que puede leer cualquiera que tenga el token. La firma (43 caracteres en HS256) es lo que impide modificarlo: cambia editor por admin y la firma deja de coincidir.

ParteContiene¿Es secreta?
HeaderEl algoritmo, como HS256No, se puede leer
PayloadClaims: sub (id de usuario), exp (expiración), iat (emitido en), tus propios camposNo, se puede leer
SignatureFirma HMAC o RSA de las dos primeras partesHace falta la clave para crearla

Expiración y verificación fallida

setExpirationTime('15m') escribe un claim exp, y jwtVerify rechaza un token que ya lo ha pasado. Cada rechazo es un error con un code, así que puedes distinguir un token expirado de uno falsificado.

La primera comprobación falla con ERR_JWT_EXPIRED y la segunda, cuya firma se cambió, con ERR_JWS_SIGNATURE_VERIFICATION_FAILED. Para tu API los dos significan lo mismo (responde 401), pero registrar el código ayuda al depurar.

Proteger una ruta de Express con un Bearer token

El cliente hace login una vez, guarda el token y lo envía en el header Authorization como Bearer <token>. Un middleware lo verifica y pone el payload en req.user para la ruta.

La primera petición, GET /me sin token, recibe 401 y Missing token. Envía POST /login con el body {"username": "ada", "password": "lovelace"}, copia el token de la respuesta y vuelve a enviar GET /me con el header Authorization: Bearer <token>: la respuesta es {"id":"user-1","name":"ada"}.

El paquete jsonwebtoken

jsonwebtoken (npm install jsonwebtoken) es la otra opción habitual. Sus sign y verify son síncronos (con callbacks opcionales) en lugar de devolver promesas:

const jwt = require('jsonwebtoken');

const token = jwt.sign({ role: 'admin' }, process.env.JWT_SECRET, {
    subject: 'user-42',
    expiresIn: '1h',
});

const payload = jwt.verify(token, process.env.JWT_SECRET); // throws if invalid

Usa jose en código nuevo: no tiene dependencias y además funciona en edge runtimes y navegadores. Los tokens de las dos librerías son JWT estándar, así que una puede verificar lo que firmó la otra.

Errores comunes

Usar decode en lugar de verify. jwt.decode y decodeJwt leen el payload sin comprobar la firma, así que un token falsificado pasa. Fíate solo de lo que devuelve verify.

Un secreto corto o subido al repositorio. Un secreto HS256 fácil de adivinar permite a un atacante firmar sus propios tokens. Usa al menos 32 bytes aleatorios, léelo de process.env y nunca lo subas al repositorio.

Tokens que nunca expiran. Sin exp, un token filtrado funciona para siempre. Pon siempre una expiración.

Preguntas frecuentes

¿Dónde debe guardar el cliente un JWT?

En una app de navegador, lo más seguro es una cookie HttpOnly y Secure, porque los scripts de la página no pueden leerla. localStorage es más sencillo, pero cualquier script de la página, incluido uno inyectado, puede leer el token. Las apps móviles usan el almacenamiento seguro de la plataforma.

¿Cómo cierro la sesión de un usuario con JWT?

El servidor no puede anular un token que ya firmó; sigue siendo válido hasta que expira. Bórralo en el cliente, haz que los access tokens duren poco y, si necesitas un logout inmediato, guarda en el servidor una lista de ids de tokens revocados (jti) y compruébala en cada petición.

¿HS256 o RS256?

HS256 usa un único secreto compartido para firmar y verificar, lo que encaja con un solo backend. RS256 firma con una clave privada y verifica con una clave pública, así que otros servicios pueden comprobar tokens sin poder crearlos.

¿Cuánto tiempo debe ser válido un JWT?

Los access tokens suelen ser válidos entre 5 y 60 minutos. Para sesiones más largas, combínalos con un refresh token guardado con más cuidado, que el cliente cambia por un access token nuevo.

Ilustración de los lenguajes de programación de Coddy

Aprende a programar con Coddy

Comenzar