Código en GitHub: building-realtime-webapp. Release: auth-passport.

Entorno de desarrollo en Heroku: building-realtime-webapp.

Giant Squid

En el artículo anterior vimos cómo implementar un sistema de autenticación básica, y en esta ocasión vamos a implementar el sistema de autenticación usando Passport. Passport es un middleware de autenticación para Node nos permitirá modularizar la autenticación de usuarios en nuestra aplicación y realizar inicio de sesión único con OAuth como Facebook o Twitter.

Instalar y configurar Passport

Lo primero que tenemos que hacer es instalar Passport y los módulos que vayamos a utilizar. Empezaremos con una autenticación contra nuestra base de datos, así que necesitamos:

npm install --save passport passport-local

Passport por defecto buscará en las peticiones el parámetro username y password, aunque podríamos cambiarlo, hemos optado por incluir el atributo username al modelo User (queda mejor que mostrar el email y podremos jugar con las rutas y el user name más adelante).

Recomiendo echarle un ojo a la Documentación de Passport local.

Una vez instalado creamos un fichero de configuración en el directorio config llamado passport.js, el cual será cargado por Sails automáticamente al lanzar el servidor. Para no copiar todo el fichero aquí puesto que está disponible en GitHub voy a destacar lo más importante. Indicamos a passport que use una estrategia local, buscando un usuario cuyo username o email coincida con el campo username de nuestro fomulario de login. Una vez encontrado el usuario comparamos las contraseñas y devolvemos el usuario.

config/passport.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
passport.use(new LocalStrategy(
	// options by default not needed.
	//{
	//	usernameField: 'username',
	//	passwordField: 'password'
	//},
	function(username, password, done) {
		process.nextTick(function () {
			User.findOne().where({
				or: [
					{ username: username },
					{ email: username }
				]
			}).done(function(err, user) {
				if (err) { return done(null, err); }
				if (!user) { return done(null, false, { message: 'Unknown user ' + username }); }
				bcrypt.compare(password, user.password, function(err, res) {
					if (!res) return done(null, false, { message: 'Invalid Password'});
					return done(null, user, { message: 'Logged In Successfully'} );
				});
			})
		});
	}
));

Unas lineas más abajo cerramos el fichero passport.js declarando el middleware de Express y asignando a las variables locales el usuario que devolvimos en el fragmento de código anterior:

config/passport.js
1
2
3
4
5
6
7
8
9
10
11
12
13
module.exports = {
	express: {
		customMiddleware: function(app){
			sails.log.info('Express midleware for passport');
			app.use(passport.initialize());
			app.use(passport.session());
			app.use(function(req,res,next){
				res.locals.loggedUser = req.user;
				next();
			});
		}
	}
};

Acciones Login y Logout

Ahora que tenemos configurado Sails para usar Passport realizamos los cambios en el controlador UserController para utilizar este nuevo medio de autenticación. Nuestras acciones login y logoutquedan mucho más reducidas puesto que gran parte del trabajo lo hace Passport con las funciones login y logout que trae implementadas.

api/controllers/UserController.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
[]
login: function(req, res, next) {
	// Use Passport LocalStrategy
	require('passport').authenticate('local', function(err, user, info){
		if ((err) || (!user)) next(err);
		req.login(user, function(err){
			if (err) return res.redirect('/user/auth');
			// Redirect to the user page.
			return res.redirect('/user/' + user.id);
		});
	})(req, res);
},
logout: function(req, res){
	// Call Passport method to destroy the session.
	req.logout();
	// Redirect to home page.
	return res.redirect('/');
}
[]

Políticas

Como Passport nos facilita unos cuantos métodos para gestionar la autenticación del usuario vamos a aprovecharlos para usarlos en nuestras políticas. El primero de ellos es el método isAuthenticated(), el cual utilizaremos en la política con dicho nombre:

api/policies/isAuthenticated.js
1
2
3
4
module.exports = function(req, res, next) {
	if ( req.isAuthenticated() ) return next();
	return res.forbidden('You are not permitted to perform this action.');
};

Y otra de las tareas que automatiza es guardar el usuario registrado en la variable req.user, la cual usaremos para comprobar si puede administrar un usuario concreto.

api/policies/canAdminUser.js
1
2
3
4
module.exports = function(req, res, next) {
	if (req.param('id') === req.user.id) return next();
	return res.forbidden('You are not permitted to perform this action.');
};

Actualización de vistas

Y los últimos cambios que debemos realizar son en las vistas, los cuales enumero:

  • En los condicionales ya no usaremos session.authenticated, si no loggedUser.
  • Para obtener los datos del usuario registrado utilizaremos loggedUser en lugar de session.user.
  • En el formulario de autenticación, utilizaremos los campos username y password, indicando que el nombre de usuario puede ser también el correo-e.
  • Añadiremos el campo username al formulario de registro y de edición.
  • Mostraremos el nombre de usuario en lugar del mail en la ficha de usuario y en la barra de navegación.

Código en GitHub: building-realtime-webapp. Release: auth-passport.

Entorno de desarrollo en Heroku: building-realtime-webapp.

Comments