Introduccion a la autenticacion con JWT
La autenticacion basada en tokens permite verificar la identidad de un usuario sin mantener estado en el servidor. Un JSON Web Token es una cadena compacta y segura para URL que representa reclamaciones firmadas digitalmente. El cliente envia el token en cada solicitud protegida y el servidor lo verifica con la clave secreta o publica correspondiente.
En un flujo tipico el usuario envia credenciales al endpoint de login. El servidor valida la contrasena, genera un token firmado con el identificador del usuario y lo devuelve. El cliente lo almacena y lo incluye en el encabezado Authorization de las solicitudes posteriores. Esta arquitectura escala bien porque el servidor no necesita consultar una sesion almacenada en cada peticion.
En 2026 las practicas han evolucionado hacia tokens de acceso de corta duracion, almacenamiento en cookies httpOnly cuando es posible, validacion estricta del algoritmo y uso de variables de entorno para los secretos. Esta guia muestra como anadir autenticacion y autorizacion a una API REST de Node.js con Express, actualizada a estandares modernos.
Configuracion del proyecto y dependencias
Se asume una API REST existente con Express y un modelo de usuario en MongoDB. Se crean los archivos de configuracion y los controladores necesarios. El secreto del token nunca debe estar hardcodeado en el codigo fuente.
// config.js
module.exports = {
secret: process.env.JWT_SECRET,
};
Se instalan las dependencias principales:
npm install jsonwebtoken bcryptjs
El controlador de autenticacion importa Express, el modelo de usuario, bcrypt y jsonwebtoken. Se configura body-parser o se usa el middleware nativo de Express para JSON.
Registro de usuarios y emision de tokens
El endpoint de registro recibe nombre, correo y contrasena. La contrasena se hashea con bcrypt antes de almacenarse. Una vez creado el usuario se firma un token que contiene su identificador.
router.post("/register", async (req, res) => {
try {
const hashedPassword = await bcrypt.hash(req.body.password, 10);
const user = await User.create({
name: req.body.name,
email: req.body.email,
password: hashedPassword,
});
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
expiresIn: "15m",
algorithm: "HS256",
});
res.status(201).json({ auth: true, token });
} catch (err) {
res.status(500).json({ message: "Error al registrar el usuario" });
}
});
El tiempo de expiracion corto limita el dano si el token se compromete. El algoritmo se especifica de forma explicita.
Login y verificacion de credenciales
El endpoint de login busca el usuario por correo, compara la contrasena proporcionada con el hash almacenado y, si coincide, emite un nuevo token.
router.post("/login", async (req, res) => {
try {
const user = await User.findOne({ email: req.body.email });
if (!user)
return res.status(404).json({ message: "Usuario no encontrado" });
const passwordIsValid = await bcrypt.compare(
req.body.password,
user.password
);
if (!passwordIsValid)
return res.status(401).json({ auth: false, token: null });
const token = jwt.sign({ id: user._id }, process.env.JWT_SECRET, {
expiresIn: "15m",
algorithm: "HS256",
});
res.json({ auth: true, token });
} catch (err) {
res.status(500).json({ message: "Error en el servidor" });
}
});
Middleware de verificacion de tokens
Un middleware extrae el token del encabezado, lo verifica y adjunta el usuario decodificado a la peticion. Las rutas protegidas lo utilizan antes de ejecutar su logica.
function verifyToken(req, res, next) {
const token = req.headers["authorization"]?.split(" ")[1];
if (!token)
return res
.status(401)
.json({ auth: false, message: "No se proporciono token" });
jwt.verify(
token,
process.env.JWT_SECRET,
{ algorithms: ["HS256"] },
(err, decoded) => {
if (err)
return res
.status(401)
.json({ auth: false, message: "Token invalido" });
req.userId = decoded.id;
next();
}
);
}
Se recomienda validar siempre la lista de algoritmos permitidos para evitar ataques de confusion de algoritmo. Las rutas se protegen montando el middleware:
router.get("/me", verifyToken, async (req, res) => {
const user = await User.findById(req.userId).select("-password");
if (!user)
return res.status(404).json({ message: "Usuario no encontrado" });
res.json(user);
});
La proyeccion omite el campo de contrasena en la respuesta.
Mejores practicas de seguridad en 2026
Los secretos deben residir en variables de entorno o en un gestor de secretos. Los tokens de acceso deben expirar en minutos. Para sesiones largas se implementa un flujo de refresh tokens almacenados de forma segura, preferiblemente en cookies httpOnly con atributos Secure y SameSite.
Nunca se almacena el token en localStorage si la aplicacion es vulnerable a XSS. Se valida el emisor y la audiencia cuando hay multiples servicios. Se utiliza HTTPS en todos los entornos. Se aplican limites de tasa en los endpoints de autenticacion. El hashing de contrasenas usa un factor de coste adecuado y se actualiza periodicamente.
En arquitecturas de microservicios se prefiere firmado asimetrico con RS256 o ES256 y publicacion de claves publicas mediante JWKS. El middleware rechaza tokens sin expiracion o con algoritmos no permitidos.
Integracion y pruebas
Los controladores se montan en la aplicacion principal bajo un prefijo como /api/auth. Se prueban los endpoints con una herramienta de cliente HTTP enviando el cuerpo en JSON y el token en el encabezado Authorization como Bearer. Se verifica que las rutas protegidas rechacen solicitudes sin token valido y que la informacion sensible no se exponga.
Conclusiones
La implementacion de autenticacion con JWT en una API REST de Node.js proporciona un mecanismo escalable y sin estado para verificar usuarios. Combinada con hashing seguro de contrasenas, middleware de verificacion, tokens de corta duracion y configuracion correcta de secretos, constituye una base solida. Las practicas de 2026 enfatizan la validacion estricta de algoritmos, el almacenamiento seguro y la separacion de tokens de acceso y renovacion. Aplicar estas tecnicas de forma consistente protege los recursos de la API y reduce la superficie de ataque. El codigo resultante es mantenible y se adapta tanto a aplicaciones monoliticas como a entornos distribuidos.
