4.2 · Autenticación y seguridad (JWT, bcrypt, Passport, Helmet)

Nivel 4 · API, autenticación y tiempo real

4.2Autenticación y seguridad (JWT, bcrypt, Passport, Helmet)

Objetivo: Asegurar la autenticación (contraseñas hasheadas con bcrypt, JWT verificados, autorización) y aplicar las bases de seguridad (Helmet, secretos protegidos).
Tiempo estimado: 13 min · lectura autónoma · Si solo tienes 5 min: lee «Lo esencial en 30s» + el esquema.

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.

Error frecuente de interpretación: « Puse la autenticación con un JWT, así que mi API es segura ». No. Un JWT solo mantiene la identidad : sin bcrypt (contraseñas en claro), sin secreto protegido (clave en duro en el código subido a Git), sin Helmet ni HTTPS, sin validación de las entradas y sin distinción 401/403, el edificio sigue siendo un colador. La seguridad es un conjunto de medidas, no un solo ladrillo.
El ciclo autenticado Inscripción → conexión → peticiones verificadas Inscripción bcrypt hashea,se almacena el hashConexión Se compara, seemite un JWT firmadoPeticiones El cliente devuelveel JWT (Authorization)Middleware Verifica : 401o 403, o req.user
Inscripción → conexión → peticiones verificadas
Versión en texto del esquema (SEO / lectores de pantalla / traducción): 1) En la inscripción, Salim hashea la contraseña con bcrypt y solo almacena el hash. 2) En la conexión, compara y luego emite un JWT firmado. 3) El cliente devuelve ese token en la cabecera Authorization en cada petición. 4) Un middleware lo verifica en las rutas protegidas : 401 si falta o es inválido, 403 si no hay permiso. Lección : hashear, firmar, verificar — con el secreto fuera del código.

🔎 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

  1. Hashee las contraseñas con bcrypt en la inscripción (nunca en claro) y compare en la conexión (bcrypt.compare).
  2. Emita un JWT firmado tras la conexión exitosa, con un secreto leído desde process.env (jamás en duro).
  3. Escriba un middleware que VERIFIQUE el JWT en las rutas protegidas (401 si falta/inválido) y adjunte el usuario a req.user.
  4. Añada Helmet, guarde todos los secretos en variables de entorno (.env ignorado por Git) y distinga 401 / 403.
Asegura la autenticación (contraseñas hasheadas, JWT verificados, autorización) y aplica las bases (Helmet, secretos protegidos) — una responsabilidad profesional.

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.

Prudencia: La seguridad compromete su responsabilidad : nunca una contraseña en claro (bcrypt obligatorio), secretos JWT y claves en variables de entorno (.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.

Lista de salida de la sección
  • Puedo explicar bcrypt, JWT, 401 vs 403.
  • Sé colocar un secreto en variable de entorno.
  • Tengo 1 ruta protegida probada con y sin token.
Autoevaluación 1-5 : ¿ningún secreto en duro en mi código?¿mis rutas sensibles distinguen 401 y 403?

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.

Comprueba tu comprensión

¿Dónde debe vivir el secreto de firma JWT?

Recursos adicionales (opcional, para ir más allá)

Examen del nivel 4Preguntas inéditas y más difíciles para validar tus conocimientos