libro / blog / autenticacion-jwt-nodejs

// autenticación

Autenticación con JWT en Node.js y Express paso a paso

Gustavo Arias10 min de lectura

TL;DR Guarda las contraseñas con bcrypt. En el login, si la contraseña es correcta, firma un JWT de vida corta con el id y el rol del usuario. En cada petición protegida, un middleware verifica la firma y la caducidad del token antes de dejar pasar. Nunca pongas datos sensibles dentro del token: se puede leer.

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ó.

$ 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

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