4.2Autenticación y seguridad (JWT, bcrypt, Passport, Helmet)
Lo esencial en 30 segundos
- Nunca una contraseña en claro : se almacena el hash bcrypt (unidireccional, con sal, lento) y se compara al conectar sin descifrar jamás.
- JWT = token firmado autoportador : emitido tras conectar, devuelto en la cabecera Authorization, verificado por un middleware (401 no autenticado, 403 no autorizado).
- Reflejo de Salim : secreto JWT en variable de entorno (jamás en duro), Helmet activado, y « no confiar nunca en las entradas ».
Mini-caso hilado — Salim asegura las cuentas de la API
La API de productos de Salim (4.1) ahora debe gestionar vendedores : inscripción, conexión, rutas reservadas. Primer reflejo : hashear cada contraseña con bcrypt antes de almacenar, luego emitir un JWT firmado tras la conexión exitosa. Escribe un middleware que verifica el token en las rutas protegidas, distingue 401 (no conectado) y 403 (sin permiso), activa Helmet y guarda su secreto JWT en una variable de entorno. Cuando un vendedor se conecta, su cliente devuelve el token en cada petición — sin exponer jamás una contraseña. Segundo hito del hilo : cuentas inviolables incluso si la base se filtra.
Continuación del caso 4.1 — Salim protege la API que acaba de construir.
1. Las contraseñas : bcrypt, nunca en claro
Regla absoluta : nunca se almacena una contraseña en claro. Se almacena su hash — una transformación unidireccional, imposible de invertir. La librería estándar es bcrypt : hashea con una sal aleatoria (contra las tablas precalculadas) y deliberadamente lento (contra la fuerza bruta). En la inscripción se hashea y se almacena el hash ; en la conexión se compara (bcrypt.compare) sin descifrar jamás. Si la base es robada, las contraseñas siguen protegidas.
Nunca programe su propio hash « casero » : use bcrypt o un equivalente probado. Y recuerde que muchos usuarios reutilizan sus contraseñas en otros sitios : una fuga en claro los expone mucho más allá de su aplicación.
2. Mantener la sesión : JWT
HTTP no tiene estado : tras la conexión, hay que reconocer al usuario en cada petición. El JWT (JSON Web Token) es un token firmado criptográficamente, emitido tras la conexión exitosa ; el cliente lo devuelve en cada petición (cabecera Authorization: Bearer ...) y el servidor lo verifica — sin almacenar sesión, el JWT es « autoportador ». Es el enfoque más popular para las API y el móvil.
Un middleware protege las rutas : token ausente o inválido → 401 ; token válido pero derechos insuficientes → 403. Passport simplifica la autenticación (estrategias locales, OAuth Google/GitHub). Retenga la distinción : autenticación (¿quién eres? 401) vs autorización (¿tienes permiso? 403).
3. La seguridad transversal
Más allá de la autenticación : Helmet (middleware Express que coloca cabeceras HTTP de seguridad), HTTPS en producción, secretos en variables de entorno (.env ignorado por Git, nunca en el código), limitación de ritmo contra la fuerza bruta, validación de las entradas (ver 4.4). El principio que lo sustenta todo : no confiar nunca en las entradas ni en el cliente — todo lo que viene del exterior es potencialmente malicioso. La seguridad no es una opción añadida al final, es una preocupación permanente, integrada desde el diseño.
🔎 Ejemplo concreto — 🔎 Ejemplo concreto — Inscripción, conexión, ruta protegida
El circuito completo de Salim, con el secreto leído desde el entorno :
POST /inscription → bcrypt.hash(motDePasse) → almacenar el hash
POST /connexion → bcrypt.compare → jwt.sign({ id }, SECRET)
GET /mon-compte → Authorization: Bearer ... → el middleware verifica
→ token ausente/inválido : 401 · sin permiso : 403
Lectura : tres rutas, tres responsabilidades distintas.
Lección : autenticación (¿quién?) y autorización (¿qué permiso?) son dos filtros.
Vocabulario de la sección
- bcrypt
- Librería de hash de contraseñas : unidireccional, con sal aleatoria, deliberadamente lenta contra la fuerza bruta.
Ejemplo sencillo: Salim hashea en la inscripción y compara en la conexión, sin descifrar jamás.
Lo que no es: un cifrado reversible o un hash « casero ».
Reflejo: contraseña en claro en base = falta grave, siempre hashear.
- JWT
- Token firmado emitido tras la conexión, devuelto por el cliente en cada petición y verificado por el servidor ; sin estado, ideal para las API.
Ejemplo sencillo: El vendedor de Salim recibe su JWT en la conexión y lo devuelve en Authorization.
Lo que no es: una sesión almacenada en el servidor con cookie.
Reflejo: verificar la firma en cada petición protegida.
- Autenticación vs autorización
- Autenticación : QUIÉN es el usuario (401 si no). Autorización : si tiene el PERMISO (403 si no).
Ejemplo sencillo: Salim devuelve 401 sin token, 403 para un vendedor en una ruta admin.
Lo que no es: un solo control difuso que mezcla ambos.
Reflejo: elegir el código correcto : ¿401 o 403?
- Helmet
- Middleware Express que define cabeceras HTTP de seguridad que protegen contra fallos web clásicos.
Ejemplo sencillo: Salim lo activa en una línea : app.use(helmet()).
Lo que no es: un sistema de autenticación completo por sí solo.
Reflejo: activarlo por defecto en toda app Express.
- Variable de entorno
- Configuración leída desde el entorno (archivo .env ignorado por Git), único lugar aceptable para los secretos.
Ejemplo sencillo: El secreto JWT de Salim vive en process.env.JWT_SECRET, nunca en el código.
Lo que no es: una clave pegada en duro en el fuente y subida a Git.
Reflejo: .env ignorado por Git + ejemplo .env.example sin valores.
En la práctica — En práctica — Asegurar la autenticación
- Hashee las contraseñas con bcrypt en la inscripción (nunca en claro) y compare en la conexión (bcrypt.compare).
- Emita un JWT firmado tras la conexión exitosa, con un secreto leído desde process.env (jamás en duro).
- Escriba un middleware que VERIFIQUE el JWT en las rutas protegidas (401 si falta/inválido) y adjunte el usuario a req.user.
- Añada Helmet, guarde todos los secretos en variables de entorno (.env ignorado por Git) y distinga 401 / 403.
Guion listo para decir — explicar sus opciones de seguridad en revisión (30 s)
« Soy Salim : ninguna contraseña en claro, todo está hasheado con bcrypt ; tras la conexión emitimos un JWT firmado cuyo secreto vive en variable de entorno, nunca en el código. Un middleware verifica el token en las rutas protegidas — 401 sin token válido, 403 sin permiso — y Helmet coloca nuestras cabeceras de seguridad. »
Para decir en revisión de código ; muestre el .gitignore que excluye .env.
Modelo copiar-pegar — auth bcrypt + JWT + Helmet (código probado)
require('dotenv').config();
const express = require('express');
const helmet = require('helmet');
const bcrypt = require('bcrypt');
const jwt = require('jsonwebtoken');
const app = express();
app.use(helmet());
app.use(express.json());
const SECRET = process.env.JWT_SECRET; // ¡jamás en duro!
if (!SECRET) throw new Error('JWT_SECRET faltante : defínalo en .env');
const vendeurs = []; // demo : en real, base de datos
// Inscripción : se hashea, se almacena el hash
app.post('/inscription', async (req, res) => {
if (!req.body.email || !req.body.motDePasse) {
return res.status(400).json({ erreur: 'Email y contraseña requeridos' });
}
const hachage = await bcrypt.hash(req.body.motDePasse, 10);
vendeurs.push({ email: req.body.email, hachage });
res.status(201).json({ message: 'Cuenta creada' });
});
// Conexión : se compara, se emite un JWT
app.post('/connexion', async (req, res) => {
const vendeur = vendeurs.find((v) => v.email === req.body.email);
if (!vendeur) return res.status(401).json({ erreur: 'Credenciales inválidas' });
const ok = await bcrypt.compare(req.body.motDePasse, vendeur.hachage);
if (!ok) return res.status(401).json({ erreur: 'Credenciales inválidas' });
const jeton = jwt.sign({ email: vendeur.email }, SECRET, { expiresIn: '1h' });
res.json({ jeton });
});
// Middleware : verifica el JWT de las rutas protegidas
function verifierJeton(req, res, next) {
const entete = req.headers.authorization || '';
const jeton = entete.replace('Bearer ', '');
if (!jeton) return res.status(401).json({ erreur: 'Token faltante' });
try {
req.utilisateur = jwt.verify(jeton, SECRET);
next();
} catch (err) {
return res.status(401).json({ erreur: 'Token inválido' });
}
}
app.get('/mon-compte', verifierJeton, (req, res) => {
res.json({ email: req.utilisateur.email });
});
app.listen(3000);
Proyecto personal pequeño
bcrypt + JWT + Helmet bastan : no complique con OAuth mientras lo local cubra la necesidad.
Conexión vía Google/GitHub
Añada Passport con una estrategia OAuth : delegue la identidad, conserve el JWT para sus propias rutas.
API expuesta en producción
Como Salim : HTTPS obligatorio, JWT de corta duración, limitación de ritmo en /connexion contra la fuerza bruta.
Equipo que empieza
Ritual no negociable : ningún secreto en el código ni en Git — revisión sistemática antes de cada push.
.env ignorado por Git, jamás en duro en el código), Helmet activado y HTTPS en producción. Los ejemplos de esta guía usan datos ficticios únicamente — ningún secreto real. En caso de duda sobre datos personales, haga validar la arquitectura por un referente técnico. Contenido pedagógico, no una auditoría de seguridad.✓ Buenas prácticas
- Hashear con bcrypt, comparar sin descifrar.
- Secreto JWT en variable de entorno, jamás en duro.
- Distinguir 401 (¿quién?) y 403 (¿qué permiso?).
✗ Errores frecuentes
- Almacenar contraseñas en claro.
- Pegar el secreto JWT en el código o en Git.
- Confundir autenticación y autorización.
Puntos clave
- NUNCA una contraseña en claro : HASH BCRYPT (unidireccional, sal, lento) ; comparar en la conexión. Nunca un hash casero.
- JWT : token firmado autoportador (Authorization), verificado por middleware ; Passport simplifica (local, OAuth).
- AUTENTICACIÓN (401) vs AUTORIZACIÓN (403) — dos controles distintos.
- Helmet, HTTPS, SECRETOS en variables de entorno, rate limiting, validación. Principio : NO CONFIAR NUNCA en las entradas.
Reto 48h — aplica mañana
Añada inscripción + conexión + 1 ruta protegida a su API : bcrypt, JWT vía process.env, Helmet — luego verifique el 401 sin token.
- Puedo explicar bcrypt, JWT, 401 vs 403.
- Sé colocar un secreto en variable de entorno.
- Tengo 1 ruta protegida probada con y sin token.
Preguntas frecuentes
¿Por qué no almacenar nunca las contraseñas en claro, y cómo protege bcrypt?
Si la base se filtra (hackeo, copia robada — ocurre regularmente), las contraseñas en claro exponen todas las cuentas, y peor, los otros servicios donde los usuarios reutilizan la misma contraseña. El hash es unidireccional : se almacena el hash en la inscripción, se compara en la conexión, sin poder « recuperar » jamás la contraseña. bcrypt añade una sal aleatoria (contra las tablas precalculadas) y una lentitud deliberada (contra la fuerza bruta). Robar hashes bcrypt no permite conectarse directamente.
¿Por dónde empezar cuando la seguridad parece intimidante?
Por la mentalidad « no confiar nunca en las entradas » — por sí sola previene gran parte de los fallos — luego por los fundamentos de alto impacto : bcrypt para las contraseñas, secretos en variables de entorno (nunca en Git), HTTPS en producción, autenticación y autorización correctas (401/403), validación de las entradas (4.4), Helmet. La seguridad se integra desde el diseño, no al final.
¿Dónde debe vivir el secreto de firma JWT?