Proceso de login
El proceso de login en Express constituye el mecanismo mediante el cual verificamos las credenciales de un usuario y determinamos si debe obtener acceso a nuestra aplicación. A diferencia del registro, donde creamos nuevos usuarios, el login valida usuarios existentes comparando las credenciales proporcionadas con las almacenadas en nuestra base de datos.
Estructura básica del endpoint de login
Un endpoint de login típico en Express recibe las credenciales del usuario (generalmente email y contraseña) y realiza una serie de validaciones antes de conceder acceso. La estructura fundamental sigue este patrón:
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
// Validación de entrada
if (!email || !password) {
return res.status(400).json({
error: 'Email y contraseña son requeridos'
});
}
// Buscar usuario en la base de datos
// Verificar contraseña
// Generar respuesta
} catch (error) {
res.status(500).json({ error: 'Error interno del servidor' });
}
});
Búsqueda y validación del usuario
El primer paso crítico es localizar al usuario en nuestra base de datos utilizando el email proporcionado. Este proceso debe manejar tanto el caso donde el usuario existe como donde no existe:
// Simulando una función de búsqueda en base de datos
const findUserByEmail = async (email) => {
// En una implementación real, esto consultaría tu base de datos
const users = [
{
id: 1,
email: 'usuario@ejemplo.com',
password: '$2b$10$hashedPasswordExample...'
}
];
return users.find(user => user.email === email);
};
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({
error: 'Email y contraseña son requeridos'
});
}
// Buscar usuario por email
const user = await findUserByEmail(email);
if (!user) {
return res.status(401).json({
error: 'Credenciales inválidas'
});
}
// Continuar con verificación de contraseña...
} catch (error) {
res.status(500).json({ error: 'Error interno del servidor' });
}
});
Verificación de contraseña con bcrypt
Una vez localizado el usuario, debemos verificar la contraseña comparando la contraseña en texto plano proporcionada con el hash almacenado. Bcrypt proporciona el método compare()
específicamente para esta tarea:
import bcrypt from 'bcrypt';
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({
error: 'Email y contraseña son requeridos'
});
}
const user = await findUserByEmail(email);
if (!user) {
return res.status(401).json({
error: 'Credenciales inválidas'
});
}
// Verificar contraseña
const isPasswordValid = await bcrypt.compare(password, user.password);
if (!isPasswordValid) {
return res.status(401).json({
error: 'Credenciales inválidas'
});
}
// Login exitoso - proceder con generación de token
res.status(200).json({
message: 'Login exitoso',
userId: user.id
});
} catch (error) {
res.status(500).json({ error: 'Error interno del servidor' });
}
});
Consideraciones de seguridad en el proceso
El manejo de errores durante el login requiere especial atención desde una perspectiva de seguridad. Es importante no revelar información que pueda ayudar a atacantes a enumerar usuarios válidos en el sistema:
Mensajes de error consistentes:
// ❌ Evitar - Revela información sobre usuarios existentes
if (!user) {
return res.status(401).json({ error: 'Usuario no encontrado' });
}
if (!isPasswordValid) {
return res.status(401).json({ error: 'Contraseña incorrecta' });
}
// ✅ Correcto - Mensaje genérico para ambos casos
if (!user || !isPasswordValid) {
return res.status(401).json({ error: 'Credenciales inválidas' });
}
Implementación completa del proceso de login
Integrando todos los conceptos anteriores, una implementación robusta del proceso de login en Express incluye validaciones, búsqueda de usuario, verificación de contraseña y manejo apropiado de errores:
import express from 'express';
import bcrypt from 'bcrypt';
const app = express();
app.use(express.json());
// Simulación de base de datos
const users = [
{
id: 1,
email: 'admin@empresa.com',
password: '$2b$10$rQZ8vHFx.5qJ9X2YvK3zLOeF4Hx8vGt2Nw9Qp1Rt5Sx7Uy3Vz6Wa8'
}
];
const findUserByEmail = async (email) => {
return users.find(user => user.email === email);
};
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
// Validación de entrada
if (!email || !password) {
return res.status(400).json({
error: 'Email y contraseña son requeridos'
});
}
// Validación básica de formato de email
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
return res.status(400).json({
error: 'Formato de email inválido'
});
}
// Buscar usuario
const user = await findUserByEmail(email);
// Verificar contraseña (incluso si el usuario no existe para evitar timing attacks)
const isPasswordValid = user ?
await bcrypt.compare(password, user.password) :
await bcrypt.compare(password, '$2b$10$dummy.hash.to.prevent.timing.attacks');
if (!user || !isPasswordValid) {
return res.status(401).json({
error: 'Credenciales inválidas'
});
}
// Login exitoso
res.status(200).json({
message: 'Login exitoso',
user: {
id: user.id,
email: user.email
}
});
} catch (error) {
console.error('Error en login:', error);
res.status(500).json({ error: 'Error interno del servidor' });
}
});
app.listen(3000, () => {
console.log('Servidor ejecutándose en puerto 3000');
});
Esta implementación establece las bases sólidas para un sistema de autenticación, manejando adecuadamente las validaciones de entrada, la búsqueda de usuarios, la verificación de contraseñas y los aspectos de seguridad fundamentales. El siguiente paso natural será la generación de tokens JWT para mantener la sesión del usuario autenticado.
¿Te está gustando esta lección?
Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.
Más de 25.000 desarrolladores ya confían en CertiDevs
Generación de JWT
Los JSON Web Tokens (JWT) representan el mecanismo estándar para mantener la sesión de usuarios autenticados en aplicaciones Express modernas. Una vez verificadas las credenciales durante el proceso de login, generamos un token que el cliente puede utilizar para acceder a recursos protegidos sin necesidad de reenviar las credenciales en cada petición.
Instalación y configuración de jsonwebtoken
Para trabajar con JWT en Express, utilizamos la librería jsonwebtoken
, que proporciona las funciones necesarias para crear y verificar tokens:
npm install jsonwebtoken
La configuración básica requiere definir una clave secreta que se utilizará para firmar los tokens. Esta clave debe mantenerse segura y nunca exponerse en el código fuente:
import jwt from 'jsonwebtoken';
// En producción, esta clave debe estar en variables de entorno
const JWT_SECRET = process.env.JWT_SECRET || 'tu-clave-secreta-muy-segura';
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';
Estructura y contenido del token
Un JWT contiene información sobre el usuario autenticado en su payload. Es importante incluir solo los datos necesarios y evitar información sensible como contraseñas:
const generateToken = (user) => {
const payload = {
userId: user.id,
email: user.email,
// Evitar incluir información sensible como contraseñas
};
const options = {
expiresIn: JWT_EXPIRES_IN,
issuer: 'mi-aplicacion',
audience: 'usuarios-aplicacion'
};
return jwt.sign(payload, JWT_SECRET, options);
};
Integración con el endpoint de login
La generación del token se integra directamente en el endpoint de login, reemplazando la respuesta simple de éxito por una que incluya el token generado:
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({
error: 'Email y contraseña son requeridos'
});
}
const user = await findUserByEmail(email);
const isPasswordValid = user ?
await bcrypt.compare(password, user.password) :
await bcrypt.compare(password, '$2b$10$dummy.hash');
if (!user || !isPasswordValid) {
return res.status(401).json({
error: 'Credenciales inválidas'
});
}
// Generar token JWT
const token = generateToken(user);
res.status(200).json({
message: 'Login exitoso',
token: token,
user: {
id: user.id,
email: user.email
}
});
} catch (error) {
console.error('Error en login:', error);
res.status(500).json({ error: 'Error interno del servidor' });
}
});
Configuración avanzada de tokens
Para aplicaciones en producción, es recomendable configurar opciones adicionales que mejoren la seguridad y funcionalidad de los tokens:
const generateToken = (user, options = {}) => {
const payload = {
userId: user.id,
email: user.email,
role: user.role || 'user',
// Timestamp de cuando se generó el token
iat: Math.floor(Date.now() / 1000)
};
const tokenOptions = {
expiresIn: options.expiresIn || JWT_EXPIRES_IN,
issuer: 'mi-aplicacion',
audience: 'usuarios-aplicacion',
// Identificador único del token
jwtid: generateJwtId(),
// Algoritmo de firma específico
algorithm: 'HS256'
};
return jwt.sign(payload, JWT_SECRET, tokenOptions);
};
// Función auxiliar para generar ID único del token
const generateJwtId = () => {
return Math.random().toString(36).substring(2) + Date.now().toString(36);
};
Manejo de diferentes tipos de tokens
En aplicaciones complejas, podemos necesitar diferentes tipos de tokens con distintas duraciones y propósitos:
const generateTokens = (user) => {
// Token de acceso de corta duración
const accessToken = jwt.sign(
{
userId: user.id,
email: user.email,
type: 'access'
},
JWT_SECRET,
{ expiresIn: '15m' }
);
// Token de refresco de larga duración
const refreshToken = jwt.sign(
{
userId: user.id,
type: 'refresh'
},
JWT_SECRET,
{ expiresIn: '7d' }
);
return { accessToken, refreshToken };
};
// Implementación en el endpoint de login
app.post('/api/auth/login', async (req, res) => {
try {
// ... validaciones anteriores ...
if (!user || !isPasswordValid) {
return res.status(401).json({
error: 'Credenciales inválidas'
});
}
const { accessToken, refreshToken } = generateTokens(user);
res.status(200).json({
message: 'Login exitoso',
accessToken: accessToken,
refreshToken: refreshToken,
user: {
id: user.id,
email: user.email
}
});
} catch (error) {
console.error('Error en login:', error);
res.status(500).json({ error: 'Error interno del servidor' });
}
});
Implementación completa con generación de JWT
La implementación final integra todos los conceptos de generación de tokens en un sistema de autenticación completo:
import express from 'express';
import bcrypt from 'bcrypt';
import jwt from 'jsonwebtoken';
const app = express();
app.use(express.json());
// Configuración JWT
const JWT_SECRET = process.env.JWT_SECRET || 'clave-secreta-desarrollo';
const JWT_EXPIRES_IN = '24h';
// Simulación de base de datos
const users = [
{
id: 1,
email: 'admin@empresa.com',
password: '$2b$10$rQZ8vHFx.5qJ9X2YvK3zLOeF4Hx8vGt2Nw9Qp1Rt5Sx7Uy3Vz6Wa8',
role: 'admin'
}
];
const findUserByEmail = async (email) => {
return users.find(user => user.email === email);
};
const generateToken = (user) => {
const payload = {
userId: user.id,
email: user.email,
role: user.role
};
const options = {
expiresIn: JWT_EXPIRES_IN,
issuer: 'mi-aplicacion'
};
return jwt.sign(payload, JWT_SECRET, options);
};
app.post('/api/auth/login', async (req, res) => {
try {
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({
error: 'Email y contraseña son requeridos'
});
}
const user = await findUserByEmail(email);
const isPasswordValid = user ?
await bcrypt.compare(password, user.password) :
await bcrypt.compare(password, '$2b$10$dummy.hash');
if (!user || !isPasswordValid) {
return res.status(401).json({
error: 'Credenciales inválidas'
});
}
// Generar token JWT
const token = generateToken(user);
res.status(200).json({
message: 'Login exitoso',
token: token,
expiresIn: JWT_EXPIRES_IN,
user: {
id: user.id,
email: user.email,
role: user.role
}
});
} catch (error) {
console.error('Error en login:', error);
res.status(500).json({ error: 'Error interno del servidor' });
}
});
app.listen(3000, () => {
console.log('Servidor ejecutándose en puerto 3000');
});
Esta implementación proporciona una base sólida para la generación de tokens JWT en aplicaciones Express, estableciendo las fundaciones necesarias para implementar posteriormente middleware de autenticación y autorización que verifiquen estos tokens en rutas protegidas.
Aprendizajes de esta lección
- Comprender el proceso de login y validación de usuarios en Express.
- Implementar la verificación segura de contraseñas usando bcrypt.
- Aprender a generar y configurar tokens JWT para mantener sesiones autenticadas.
- Integrar la generación de tokens en el endpoint de login.
- Aplicar buenas prácticas de seguridad en el manejo de errores y tokens.
Completa Express y certifícate
Únete a nuestra plataforma y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.
Asistente IA
Resuelve dudas al instante
Ejercicios
Practica con proyectos reales
Certificados
Valida tus conocimientos
Más de 25.000 desarrolladores ya se han certificado con CertiDevs