Hashea las contraseñas con una función costosa, elige entre sesiones y JWT con pleno conocimiento, y protege tus rutas con un middleware.
Abrir esta lección en KodokonAlmacenar una contraseña significa prepararse para una futura filtración: diseña el hashing para que sobreviva al robo de la base de datos. Un hash rápido como SHA-256 se puede forzar por fuerza bruta a miles de millones de intentos por segundo en una GPU. Necesitas una función deliberadamente costosa (scrypt, provista por node:crypto, o bcrypt/argon2) y un salt aleatorio por usuario, que vuelve inútiles las tablas precalculadas. El salt no es un secreto: se almacena en texto plano junto al hash.
import {
randomBytes,
scryptSync,
timingSafeEqual,
} from 'node:crypto';
export function hashPassword(password) {
const salt = randomBytes(16).toString('hex');
const hash = scryptSync(password, salt, 64)
.toString('hex');
return `${salt}:${hash}`;
}
export function verifyPassword(password, stored) {
const [salt, hash] = stored.split(':');
const candidate = scryptSync(password, salt, 64);
const expected = Buffer.from(hash, 'hex');
return timingSafeEqual(candidate, expected);
}Dos modelos para mantener al usuario con la sesión iniciada. La sesión: un identificador opaco en una cookie httpOnly, con el estado del lado del servidor. Revocación inmediata, cookie invisible para JavaScript, pero un almacén compartido (Redis) se vuelve necesario en cuanto añades una segunda instancia. El JWT: el estado firmado incrustado en el token, verificable sin ningún almacén (ideal entre servicios) pero irrevocable antes de expirar. La contrapartida honesta: sesiones para una aplicación web clásica, JWT de vida corta para APIs consumidas por terceros o para arquitecturas distribuidas.
import jwt from 'jsonwebtoken';
const SECRET = process.env.JWT_SECRET;
if (!SECRET) {
throw new Error('JWT_SECRET must be set');
}
export function signToken(user) {
return jwt.sign(
{ sub: user.id, email: user.email },
SECRET,
{ expiresIn: '15m' },
);
}El middleware de protección factoriza el control de acceso: extrae el token de la cabecera Authorization: Bearer, lo verifica, adjunta la identidad decodificada a req.user, o corta por lo sano con un 401 dirigido al middleware de errores de la lección anterior. Aplícalo a nivel del router (router.use(requireAuth)) para proteger todo un grupo de rutas en lugar de ruta por ruta.
import jwt from 'jsonwebtoken';
import { HttpError } from './http-error.js';
const SECRET = process.env.JWT_SECRET;
export function requireAuth(req, res, next) {
const header = req.headers.authorization ?? '';
const [scheme, token] = header.split(' ');
if (scheme !== 'Bearer' || !token) {
return next(new HttpError(401, 'Missing token'));
}
try {
req.user = jwt.verify(token, SECRET);
next();
} catch {
next(new HttpError(401, 'Invalid or expired token'));
}
}timingSafeEqual en la verificación de una contraseña?