Passport es un excelente paquete de npm especializado en facilitar la implementación de un sistema de Login y Registro de Usuarios que podemos usar con Express muy cómodamente.
En el capítulo anterior, preparamos el AuthCtrl.js para que haga uso de un par de funciones
req.login(), req.logout() y req.authenticate() que no hemos explicado. Estas funciones existirán en el
objeto req gracias a la integración del middleware de passport en nuestra app
(app.use(passport.initialize())
), sin embargo, antes de que Passport
pueda hacer nada, precisamos de realizar una configuracion previa. Esta configuración es lo que se
llama una Estrategia.
Ahora hay que decirle a Passport qué Estrategia usar. Esto es, qué mecanismo debe realizar por debajo una vez posea las credenciales de un Usuario para determinar si dicho usuario puede considerarse autenticado o no. Para este proyecto usaremos la Passport Local, la estrategia más simple, consistente en comparar las credenciales suministradas con las existentes en nuestra BBDD (campos email y contraseña).
Primero, instalaremos dicho paquete con:
npm install -S passport-local
Ahora, crearemos un directorio config dentro de server, y en él, crearemos un fichero passport.js donde escribiremos la configuración:
var passport = require('passport');
var LocalStrategy = require('passport-local').Strategy;
var mongoose = require('mongoose');
var User = mongoose.model('User');
passport.serializeUser(function(user, done) {
done(null, user.id);
});
passport.deserializeUser(function(id, done) {
User.findById(id, function(err, user) {
done(err, user);
});
});
passport.use(new LocalStrategy(
{
usernameField: 'email',
passwordField: 'password'
},
function(email, password, done) {
User.authenticate(email, password, function(err, user, info) {
if(err) return done(err);
if(!user) return done(null, false, info);
done(null, user);
});
}
));
La función serializeUser() le indica a Passport qué atributo debe almacenar en la sesión, mientras que deserializeUser() le indica qué debe hacer con dicho atributo para poder obtener una instancia de un Usuario. De este modo Passport mantendrá memorizado qué Usuario está autenticado.
La Estrategia es usada con passport.use() donde le pasamos un objeto con los atributos que deberá usar Passport como credenciales, en este caso email y password (nombres de atributos del modelo User). También le suministramos una función de callback donde le indicamos cómo debe determinar Passport si un usuario esta autenticado o no.
Para ello crearemos en el modelo User una función authenticate().
Abrimos el fichero server/models/User.js y añadimos este nuevo método, en forma de método estático de Mongoose:
...
schema.statics.authenticate = function(email, password, callback) {
this.findOne({ email: email }, function (err, user) {
if (err) return callback(err);
if (!user) return callback(null, false, {message: 'Incorrect email.'});
if (!user.validPassword(password)) return callback(null, false, {message: 'Incorrect password.'});
return callback(null, user);
});
};
mongoose.model('User', schema);
El método autheticate simplemente usará a el método findOne() de Mongoose para realizar una búsqueda de un Usuario a partir del email. Si lo encuentra, procederá a usar el método validPassword() para comprobar si el hash almacenado coincide con el hash ingresado.
No debemos olvidar incluir esta configuración en nuestra Aplicación. Editaremos el archivo app.js con:
...
require('./server/models/db.js');
require('./server/config/passport.js'); //<-- Nuevo
const index = require('./server/routes/index');
...
Un poco más abajo, en el mismo archivo app.js, antes de las rutas, deberemos inicializar Passport con:
...
app.use(passport.initialize()); // <-- Nuevo
app.use(passport.session()); // <-- Nuevo
app.use('/', index);
app.use('/users', users);
app.use('/auth', auth);
...
Ahora ya tendremos la aplicación lista para mantener Usuarios logados. Antes de continuar con las vistas, voy a aportar un bonus track al sistema. Crearemos un pequeño middleware que se encargará de suministrarnos una variable user con el Usuario autenticado, de manera que tengamos acceso a dicha variable desde cualquier vista!
Puede ser muy conveniente disponer del Usuario en las vistas, para mostrar, por ejemplo, el nombre/email de un usuario a lo largo de todas las vistas (barra de navegación por ejemplo) una vez se ha logado. Para ello, crearemos un archivo Locals.js dentro de server/middlewares. En él simplemente escribiremos:
module.exports = (req, res, next) => {
// put user into res.locals for easy access from templates
res.locals.user = req.user || null;
next();
};
Una vez un Usuario se ha logado correctamente, Passport creará una variable req.user donde almacenará a dicho usuario. Con este middleware lo que hacemos es tomar dicha variable y meterla en res.locals, que es una variable especial de las requests de Express. Esta variable req.locals lo que permite es definir variables para que estén presentes en todas las vistas .
Montamos este middleware en nuestra Aplicación abriendo app.js y editando:
...
const passport = require('passport');
const locals = require('./server/middlewares/Locals'); // <-- Nuevo
require('./server/models/db.js');
...
Finalmente, lo montamos en todas las rutas:
...
app.use(passport.initialize());
app.use(passport.session());
app.use('*', locals); // <-- Nuevo
app.use('/', index);
...
Ahora, desde cualquier vista podremos acceder al usuario logado .
Y esto ha sido todo por ahora, sólo faltan añadir las vistas que serviremos a los clientes. Esto lo veremos en el próximo capítulo. Y como siempre, el código esta aquí.