Casi todas las APIs necesitan saber quién hace cada petición. JWT (JSON Web Token) se convirtió en la forma más común de resolverlo en Node.js, sobre todo cuando el frontend es una SPA o una app móvil. Pero también es fácil implementarlo mal. En el libro Node.js desde cero la autenticación y la autorización tienen un capítulo propio y se aplican en el proyecto fullstack con panel de administración; acá tenés el flujo completo y seguro.
Qué es un JWT (y qué no es)
Un JWT es un texto con tres partes separadas por puntos: cabecera.payload.firma. El payload
contiene datos (por ejemplo, el id del usuario y su rol) y la firma, hecha con una clave secreta del
servidor, garantiza que nadie los modificó.
- Está firmado, no cifrado: cualquiera puede decodificar el payload y leerlo. Nunca pongas contraseñas ni datos sensibles dentro.
- Es autocontenido: el servidor puede validarlo sin consultar la base de datos.
- No se puede “cerrar sesión” fácilmente: un token válido sigue siéndolo hasta que caduca. Por eso conviene que viva poco.
$ npm install express jsonwebtoken bcrypt
Registro con contraseñas cifradas
import { Router } from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
import { usuarios } from '../db.js'; // tu capa de datos (MySQL, MongoDB...)
const router = Router();
const SECRETO = process.env.JWT_SECRET; // largo, aleatorio y fuera del código
router.post('/registro', async (req, res) => {
const { email, password } = req.body ?? {};
if (typeof email !== 'string' || typeof password !== 'string' || password.length < 10) {
return res.status(400).json({ error: 'Email y contraseña (mínimo 10 caracteres) son obligatorios' });
}
if (await usuarios.buscarPorEmail(email)) {
return res.status(409).json({ error: 'Ya existe una cuenta con ese email' });
}
// 12 rondas: lento a propósito para frenar ataques de fuerza bruta
const hash = await bcrypt.hash(password, 12);
const usuario = await usuarios.crear({ email, hash, rol: 'lector' });
res.status(201).json({ id: usuario.id, email: usuario.email });
});
Login: emitir el token
router.post('/login', async (req, res) => {
const { email, password } = req.body ?? {};
const usuario = await usuarios.buscarPorEmail(String(email));
// Mismo mensaje si no existe el usuario o si la contraseña es incorrecta
const ok = usuario && await bcrypt.compare(String(password), usuario.hash);
if (!ok) {
return res.status(401).json({ error: 'Credenciales incorrectas' });
}
const token = jwt.sign(
{ sub: String(usuario.id), rol: usuario.rol }, // solo lo imprescindible
SECRETO,
{ expiresIn: '15m', algorithm: 'HS256' },
);
res.json({ token });
});
export default router;
Proteger rutas con un middleware
El cliente envía el token en cada petición, en la cabecera
Authorization: Bearer <token>. Un middleware lo
verifica antes de llegar a la ruta:
import jwt from 'jsonwebtoken';
export function autenticar(req, res, next) {
const [tipo, token] = (req.headers.authorization ?? '').split(' ');
if (tipo !== 'Bearer' || !token) {
return res.status(401).json({ error: 'Falta el token' });
}
try {
// Verifica la firma y la caducidad; fija el algoritmo esperado
const payload = jwt.verify(token, process.env.JWT_SECRET, { algorithms: ['HS256'] });
req.usuario = { id: payload.sub, rol: payload.rol };
next();
} catch {
res.status(401).json({ error: 'Token inválido o caducado' });
}
}
import express from 'express';
import authRouter from './routes/auth.js';
import { autenticar } from './middleware/autenticar.js';
const app = express();
app.use(express.json());
app.use('/api/auth', authRouter);
// Ruta protegida: solo con un token válido
app.get('/api/perfil', autenticar, (req, res) => {
res.json({ id: req.usuario.id, rol: req.usuario.rol });
});
export default app;
Autorización por roles
Autenticar es saber quién sos; autorizar es decidir qué podés hacer. Con el rol dentro del token, un segundo middleware decide:
const soloAdmin = (req, res, next) =>
req.usuario?.rol === 'admin' ? next() : res.status(403).json({ error: 'Solo administradores' });
app.delete('/api/posts/:id', autenticar, soloAdmin, borrarPost);
Usá 401 cuando falta o no sirve la identificación, y 403 cuando la identidad es válida pero no tiene permiso.
Buenas prácticas de seguridad
- Secreto fuerte y fuera del código: generá al menos 32 bytes aleatorios
(
node -e "console.log(require('node:crypto').randomBytes(32).toString('hex'))") y guardalos en una variable de entorno. - Vida corta: tokens de acceso de minutos. Para no pedir el login a cada rato, se usa un refresh token de vida más larga, guardado en el servidor para poder revocarlo.
- Dónde guardarlo en el navegador:
localStoragees accesible desde JavaScript, así que un ataque XSS puede robarlo. Una cookieHttpOnly,SecureySameSitees más resistente (y exige protegerse de CSRF). - Fijá el algoritmo al verificar (
algorithms: ['HS256']) para evitar ataques de confusión de algoritmo. - Limitá los intentos de login con un rate limit (ver middleware).
- ¿JWT o sesiones? Para una aplicación web tradicional del mismo dominio, las sesiones con cookie son más simples y fáciles de revocar. JWT brilla con APIs consumidas por apps móviles o por varios servicios.
Login y autorización en proyectos reales
El libro Node.js desde cero enseña autenticación segura con JWT y sesiones, y la aplica en un sistema fullstack con login y panel de administración.
Comprar en Amazon