Autenticación con JWT paso a paso

A coder intensely typing at a workstation in a contemporary office setup.
Foto de cottonbro studio en Pexels

En los últimos años los JSON Web Tokens (JWT) se han convertido en el estándar de facto para gestionar la autenticación sin estado en aplicaciones web. En este artículo te guiaré paso a paso, desde la teoría básica hasta una implementación mínima en Node.js, con ejemplos claros y consejos de seguridad.

¿Qué es un JWT?

Un JWT es simplemente una cadena codificada en Base64URL que contiene tres partes: header, payload y signature. Estas partes están separadas por puntos (.) y permiten que el servidor verifique la integridad del token sin necesidad de almacenar sesión alguna.

Estructura de un token

Veamos cada bloque:

  • Header: indica el algoritmo de firma (por ejemplo HS256) y el tipo (JWT).
  • Payload: lleva los claims, datos que describen al usuario (id, email, roles…) y campos reservados como exp (expiración) o iat (emisión).
  • Signature: es el resultado de firmar header.payload con una clave secreta o una clave privada, garantizando que nadie pueda modificar el token.

Flujo típico de autenticación

1. El cliente envía credenciales (usuario/contraseña) al endpoint /login.
2. El servidor valida esas credenciales contra la base de datos.
3. Si son correctas, genera un JWT y lo devuelve al cliente.
4. El cliente almacena el token (habitualmente en localStorage o en una cookie httpOnly) y lo incluye en el encabezado Authorization: Bearer <token> de cada petición protegida.
5. El servidor, al recibir la petición, verifica la firma y la validez temporal del token antes de conceder acceso.

Implementación mínima en Node.js con Express

Primero instalamos las dependencias:

npm install express jsonwebtoken dotenv

Crearemos un archivo .env con la clave secreta:

JWT_SECRET=mi_clave_ultra_secreta

A continuación, el código de ejemplo:

require('dotenv').config();
const express = require('express');
const jwt = require('jsonwebtoken');
const app = express();
app.use(express.json());

// Simulación de usuarios
const users = [{ id: 1, email: 'alice@example.com', password: '1234' }];

app.post('/login', (req, res) => {
  const { email, password } = req.body;
  const user = users.find(u => u.email === email && u.password === password);
  if (!user) return res.status(401).json({ message: 'Credenciales inválidas' });

  const token = jwt.sign({ id: user.id, email: user.email }, process.env.JWT_SECRET, { expiresIn: '1h' });
  res.json({ token });
});

// Middleware de protección
function authenticateToken(req, res, next) {
  const authHeader = req.headers['authorization'];
  const token = authHeader && authHeader.split(' ')[1];
  if (!token) return res.sendStatus(401);
  jwt.verify(token, process.env.JWT_SECRET, (err, user) => {
    if (err) return res.sendStatus(403);
    req.user = user; // queda disponible en la siguiente función
    next();
  });
}

app.get('/profile', authenticateToken, (req, res) => {
  res.json({ message: 'Acceso concedido', user: req.user });
});

app.listen(3000, () => console.log('Server running on http://localhost:3000'));

En este ejemplo:

  • Se genera el token con una expiración de una hora.
  • El middleware authenticateToken extrae el token del encabezado y lo verifica.
  • Si la verificación falla, devolvemos 401 (no autenticado) o 403 (token inválido).

Renovación y revocación

Los JWT son sin estado, lo que significa que el servidor no guarda una lista de tokens activos. Por eso, la estrategia típica es:

  • Emitir tokens de corta vida (15‑30 minutos).
  • Proveer un refresh token de mayor duración, almacenado en una cookie httpOnly, que permita solicitar un nuevo JWT sin volver a pedir credenciales.
  • Si necesitas revocar un token antes de que expire (por ejemplo, al cerrar sesión), guarda su jti (ID único) en una lista negra (en Redis o base de datos) y verifica contra ella en cada petición.

Buenas prácticas de seguridad

Para que tu implementación sea robusta, sigue estas recomendaciones:

  • Clave secreta fuerte: usa al menos 256 bits y nunca la incluyas en el repositorio.
  • Algoritmo de firma: HS256 es suficiente en la mayoría de los casos, pero si quieres separar la generación y verificación, usa RS256 con pares de claves públicas/privadas.
  • HTTPS obligatorio: evita que el token sea interceptado en tránsito.
  • Almacena el token de forma segura: en aplicaciones SPA, localStorage es práctico pero vulnerable a XSS; las cookies httpOnly y SameSite=strict reducen ese riesgo.
  • Valida siempre el exp: la librería jsonwebtoken lo hace automáticamente, pero si implementas tu propio verificador, no lo olvides.

Con estos pasos tienes una base sólida para integrar JWT en cualquier proyecto backend. Desde aquí puedes explorar funcionalidades avanzadas como scopes, roles en el payload o incluso firmar tokens con claves asimétricas para micro‑servicios distribuidos.

¡Anímate a probarlo en tu próximo proyecto y comparte tus dudas en los comentarios!