Tutorial autenticación Node.js JWT: Sistema de autenticación seguro paso a paso
En este tutorial detallado aprenderás a crear un sistema de autenticación seguro usando Node.js, Express y JSON Web Tokens (JWT). Está dirigido a desarrolladores principiantes e intermedios interesados en implementar un login robusto, proteger rutas privadas y manejar la seguridad en sus aplicaciones web.
Índice
- 1. Configuración del entorno de desarrollo
- 2. Creación de un servidor Express básico
- 3. Diseño de rutas para registro e inicio de sesión
- 4. Encriptación de contraseñas con bcrypt
- 5. Generación y verificación de tokens JWT
- 6. Protección de rutas privadas
- 7. Manejo de errores y validación de entradas
- Conclusión y buenas prácticas
1. Configuración del entorno de desarrollo
Para empezar, asegúrate de tener instalado Node.js y npm. Puedes verificarlo con:
1 2 3 |
node -v npm -v |
Inicializa un proyecto Node.js nuevo:
1 2 3 4 |
mkdir auth-tutorial cd auth-tutorial npm init -y |
Instala las dependencias necesarias:
- express: framework para servidor.
- bcryptjs: para encriptar contraseñas.
- jsonwebtoken: para crear y validar tokens JWT.
- body-parser: para manejar solicitudes POST.
- express-validator: para validar datos de usuario.
Ejecuta:
1 2 |
npm install express bcryptjs jsonwebtoken body-parser express-validator |
2. Creación de un servidor Express básico
Crea un archivo server.js
y agrega el siguiente código básico para iniciar tu servidor:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
const express = require('express'); const bodyParser = require('body-parser'); const app = express(); const PORT = process.env.PORT || 3000; // Middleware para parsear JSON app.use(bodyParser.json()); app.get('/', (req, res) => { res.send('Servidor de autenticación con Node.js y JWT'); }); app.listen(PORT, () => { console.log(`Servidor corriendo en http://localhost:${PORT}`); }); |
Para probar, ejecuta:
1 2 |
node server.js |
Y visita http://localhost:3000
para ver el mensaje.
3. Diseño de rutas para registro e inicio de sesión
Vamos a crear dos rutas principales: /register
y /login
.
/register
: para crear usuarios nuevos./login
: para autenticarse y recibir un token JWT.
Para este tutorial usaremos una base de datos en memoria para simplificar. En proyectos reales, integra una base de datos como MongoDB o PostgreSQL.
Agrega al inicio del archivo:
1 2 |
const users = []; |
Luego define las rutas:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 |
const { check, validationResult } = require('express-validator'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const SECRET_KEY = 'tu_clave_secreta'; // Cambia esta clave por una segura en producción // Ruta registro app.post('/register', [ check('username', 'El nombre de usuario es obligatorio').not().isEmpty(), check('password', 'La contraseña debe tener al menos 6 caracteres').isLength({ min: 6 }) ], (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { username, password } = req.body; // Verificar si el usuario existe const userExists = users.find(user => user.username === username); if (userExists) { return res.status(400).json({ msg: 'El usuario ya existe' }); } // Hashear contraseña const salt = bcrypt.genSaltSync(10); const hashedPassword = bcrypt.hashSync(password, salt); // Guardar usuario users.push({ username, password: hashedPassword }); res.status(201).json({ msg: 'Usuario registrado con éxito' }); }); // Ruta login app.post('/login', [ check('username', 'El nombre de usuario es obligatorio').not().isEmpty(), check('password', 'La contraseña es obligatoria').not().isEmpty() ], (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } const { username, password } = req.body; // Buscar usuario const user = users.find(user => user.username === username); if (!user) { return res.status(400).json({ msg: 'Usuario o contraseña incorrectos' }); } // Comparar contraseña const isMatch = bcrypt.compareSync(password, user.password); if (!isMatch) { return res.status(400).json({ msg: 'Usuario o contraseña incorrectos' }); } // Crear token JWT const token = jwt.sign({ username: user.username }, SECRET_KEY, { expiresIn: '1h' }); res.json({ token }); }); |
4. Encriptación de contraseñas con bcrypt
La librería bcryptjs permite hash y saltear contraseñas para mayor seguridad.
¿Por qué usar bcrypt?
- Genera un hash irreversible.
- Añade sal para evitar ataques con tablas rainbow.
Proceso paso a paso:
- Generar un salt con
genSaltSync
. - Hashear la contraseña con el salt usando
hashSync
. - Al validar, usar
compareSync
para comparar la contraseña ingresada con la almacenada.
Código extraído del paso anterior para registrar un usuario:
1 2 3 4 5 6 7 |
// Hashear contraseña const salt = bcrypt.genSaltSync(10); const hashedPassword = bcrypt.hashSync(password, salt); // Guardar usuario con contraseña Hasheada users.push({ username, password: hashedPassword }); |
5. Generación y verificación de tokens JWT
JSON Web Tokens (JWT) nos permiten crear tokens firmados digitalmente que verifican la identidad del usuario sin necesidad de almacenar estado en el servidor.
Creación del token
Utilizamos jwt.sign(payload, secret, options)
:
1 2 3 4 5 6 |
const token = jwt.sign( { username: user.username }, SECRET_KEY, { expiresIn: '1h' } // Tiempo de expiración ); |
Verificación del token
Antes de acceder a rutas protegidas, validamos el token con jwt.verify(token, secret)
.
Ejemplo:
1 2 3 4 5 6 |
jwt.verify(token, SECRET_KEY, (err, user) => { if (err) return res.status(401).json({ msg: 'Token no válido' }); req.user = user; // Payload con información del usuario next(); }); |
6. Protección de rutas privadas
Implementemos un middleware para autenticar el token y proteger las rutas.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 |
// Middleware para validar JWT function authenticateToken(req, res, next) { const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN if (!token) return res.status(401).json({ msg: 'No se proporcionó token' }); jwt.verify(token, SECRET_KEY, (err, user) => { if (err) return res.status(403).json({ msg: 'Token no válido o expirado' }); req.user = user; next(); }); } // Ruta privada de ejemplo app.get('/profile', authenticateToken, (req, res) => { res.json({ msg: `Hola ${req.user.username}, esta es tu información privada.` }); }); |
Ahora, al hacer una petición GET a /profile
, debes enviar el token en el header:
1 2 |
Authorization: Bearer <tu_token_jwt> |
7. Manejo de errores y validación de entradas
Usamos express-validator
para validar que las entradas de usuario sean correctas y evitar errores o inyecciones.
El patrón usado:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
const { check, validationResult } = require('express-validator'); app.post('/ruta', [ check('campo', 'Mensaje error').validaciones(), ... ], (req, res) => { const errors = validationResult(req); if (!errors.isEmpty()) { return res.status(400).json({ errors: errors.array() }); } // código de ruta }); |
Además, manejamos errores personalizados para casos comunes como usuario duplicado, credenciales inválidas, token ausente o expirado.
Conclusión y buenas prácticas
En este tutorial has aprendido cómo crear un sistema de autenticación seguro con Node.js, Express y JWT, que incluye:
- Configuración de entorno y dependencias.
- Creación de servidor y rutas fundamentales.
- Encriptación segura de contraseñas con bcrypt.
- Generación y validación de JWT para autenticación sin estado.
- Protección de rutas privadas mediante middleware.
- Validación y manejo de errores para robustez.
Buenas prácticas adicionales a considerar:
- No almacenar la
SECRET_KEY
directamente en el código; usar variables de entorno con paquetes comodotenv
. - Implementar HTTPS para proteger tokens en tránsito.
- Considerar refresco de tokens si se requiere mayor seguridad.
- Usar bases de datos reales para persistencia y manejar índices para eficiencia.
- Limitar intentos de login para prevenir ataques de fuerza bruta.
Próximos pasos:
- Integrar este sistema con bases de datos como MongoDB.
- Añadir roles y permisos para control más fino.
- Mejorar la interfaz cliente para consumir estas APIs.
¡Empieza a implementar este sistema en tus proyectos y mejora la seguridad en aplicaciones Node.js!
Si te gustó este tutorial, comparte y suscríbete para más contenido sobre seguridad en aplicaciones Node.js.