Passport.js: Middleware de autenticación
Al finalizar este tema
Comprenderá la utilidad de Passport.js, podrá implementar la autenticación con ID y contraseña mediante la estrategia Local y explicará la función de serialización y deserialización.
¿Por qué usar Passport.js?
En el tema anterior, implementamos manualmente la autenticación basada en sesiones. Aunque funciona, el código se vuelve más complejo a medida que aumenta el número de métodos de autenticación.
- Inicio de sesión con ID y contraseña
- Inicio de sesión con Google
- Inicio de sesión con GitHub
- Autenticación con token JWT
Implementar cada uno manualmente dispersaría la lógica de autenticación entre las diferentes rutas. Passport.js es un middleware especializado en autenticación que resuelve este problema. Ofrece más de 500 estrategias de autenticación en forma de complementos.
Concepto clave: Patrón Strategy
Passport.js utiliza el patrón Strategy. Separa la pregunta "¿cómo autenticar?" en objetos Strategy.
Passport.js (framework)
├── Estrategia Local → ID/contraseña
├── Google Strategy → Google OAuth
├── GitHub Strategy → GitHub OAuth
└── JWT Strategy → JSON Web TokenCada estrategia es un paquete npm independiente. Instala solo lo que necesites.
Instalación
npm install passport passport-local express-sessionpassport: Biblioteca principalpassport-local: Estrategia de autenticación por ID y contraseñaexpress-session: Gestión de sesiones (Passport se utiliza internamente)
Configuración de la estrategia local
const express = require('express');
const session = require('express-session');
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
const app = express();
// Base de datos de usuarios ficticia
const users = [
{ id: 1, username: 'alice', password: 'pass123', displayName: 'Alice Kim' },
{ id: 2, username: 'bob', password: 'pass456', displayName: 'Bob Lee' }
];
// 1. Registro de estrategia
passport.use(new LocalStrategy(
(username, password, done) => {
const user = users.find(u => u.username === username);
if (!user) {
return done(null, false, { message: 'User not found' });
}
if (user.password !== password) {
return done(null, false, { message: 'Wrong password' });
}
return done(null, user);
}
));done Tres patrones de llamada a funciones de devolución:
| Llamada | Significado |
|---|---|
done(null, user) | Autenticación exitosa. Se pasa el objeto de usuario |
done(null, false, {message}) | Autenticación fallida. Usuario no encontrado o contraseña incorrecta |
done(err) | Error del sistema. Fallo en la conexión a la base de datos, etc. |
Serializar / Deserializar
Almacenar el objeto completo del usuario en la sesión consume memoria innecesariamente. Passport almacena en la sesión solo la información de identificación mínima (generalmente el ID) y restaura al usuario a partir de ese ID en cada solicitud.
// Datos a guardar en la sesión (una vez al iniciar sesión)
passport.serializeUser((user, done) => {
done(null, user.id); // Guardar solo user.id en la sesión
});
// 3. Restaurar usuario desde la sesión (en cada solicitud)
passport.deserializeUser((id, done) => {
const user = users.find(u => u.id === id);
done(null, user); // Configurar objeto de usuario completo en req.user
});Al iniciar sesión: objeto user → serializeUser → guardar id=1 en la sesión
En cada solicitud: id=1 desde la sesión → deserializeUser → restaurar objeto user → req.userAl consultar la base de datos en deserializeUser, se guarda solo el ID en la sesión, lo que permite acceder a la información del usuario más reciente en cada solicitud.
Conexión del middleware
// 4. Configuración del middleware (¡el orden es importante!)
app.use(express.json());
app.use(express.urlencoded({ extended: false }));
app.use(session({
secret: process.env.SESSION_SECRET || 'dev-secret',
resave: false,
saveUninitialized: false
}));
app.use(passport.initialize()); // Inicialización de Passport
app.use(passport.session()); // Conexión con la sesiónEl orden es importante: express-session → passport.initialize() → passport.session(). Si se altera el orden, la sesión no funcionará correctamente.
Rutas de inicio y cierre de sesión
// Inicio de sesión
app.post('/login',
passport.authenticate('local', {
successRedirect: '/dashboard',
failureRedirect: '/login',
failureMessage: true
})
);
// O cuando se desea una respuesta JSON
app.post('/api/login', (req, res, next) => {
passport.authenticate('local', (err, user, info) => {
if (err) return next(err);
if (!user) {
return res.status(401).json({ error: info.message });
}
req.logIn(user, (err) => {
if (err) return next(err);
res.json({ message: `Welcome, ${user.displayName}` });
});
})(req, res, next);
});
// Cierre de sesión
app.post('/logout', (req, res) => {
req.logout((err) => {
if (err) return res.status(500).json({ error: 'Logout failed' });
res.json({ message: 'Logged out' });
});
});passport.authenticate('local') ejecuta la estrategia LocalStrategy que se registró previamente. Si la ejecución es exitosa, se llama a serializeUser y el ID de usuario se guarda en la sesión.
Rutas protegidas
function ensureAuthenticated(req, res, next) {
if (req.isAuthenticated()) {
return next();
}
res.status(401).json({ error: 'Login required' });
}
app.get('/dashboard', ensureAuthenticated, (req, res) => {
res.json({
message: `Hello, ${req.user.displayName}`,
user: { id: req.user.id, username: req.user.username }
});
});req.isAuthenticated() es un método que proporciona Passport y que devuelve true si hay un usuario válido en la sesión. req.user es el objeto de usuario recuperado de deserializeUser.
Resumen del flujo completo
1. Iniciar servidor
passport.serializeUser() — Registrar método de almacenamiento de sesión
passport.deserializeUser() — Registrar método de restauración de sesión
passport.deserializeUser() — Registrar método de restauración de sesión
2. POST /login
passport.authenticate('local') — Ejecutar estrategia
→ Éxito: serializeUser → Guardar ID en la sesión → Respuesta
→ Fallo: respuesta 401
3. GET /dashboard (autenticación requerida)
Verificar cookie de sesión → deserializeUser → configurar req.user
ensureAuthenticated → req.isAuthenticated() → true → ejecución de ruta
4. POST /logout
req.logout() → eliminación del usuario en la sesión → respuestaErrores comunes
| Error | Consecuencia | Solución |
|---|---|---|
Llamar a passport.session() antes de app.use(session(...)) | La sesión no se inicializa | Orden: express-session → passport.initialize() → passport.session() |
Guardar el objeto user completo en serializeUser | El tamaño de la sesión aumenta excesivamente | Guardar solo user.id |
Ignorar errores asíncronos en deserializeUser | El servidor falla cuando la autenticación no se realiza correctamente | Llamar a done(err) |
Omitir la llamada a done() en la función de retorno de Strategy | La solicitud se queda en espera indefinida | Verificar que se llame a done() en todas las ramas |
Resumen
| Concepto | Descripción |
|---|---|
| Passport.js | Middleware especializado en autenticación. Admite varios métodos de autenticación mediante el patrón Strategy |
Strategy | Objeto que encapsula un método de autenticación (Local, Google, JWT, etc.) |
serializeUser | Determina la información mínima que se debe guardar en la sesión (normalmente user.id) |
deserializeUser | Restaura el objeto de usuario a partir del ID de la sesión en cada solicitud |
req.user | Usuario actualmente autenticado restaurado por deserializeUser |
req.isAuthenticated() | Verifica si el usuario ha iniciado sesión |
El valor de Passport.js reside en la "estandarización de la lógica de autenticación". Al cambiar de Local a Google OAuth, solo se debe reemplazar la Strategy. serialize/deserialize, la cadena de middleware y req.user permanecen sin cambios en el resto del código.