¿Cómo implementar la autenticación con Passport.js?
Passport.js es una herramienta valiosa para implementar capas de autenticación en aplicaciones web. Este middleware es flexible y admite múltiples estrategias de autenticación, como login con usuario y contraseña o a través de plataformas sociales como Facebook y Twitter. Además, Passport.js facilita la integración de nuevas estrategias sin modificar la estructura de nuestro código base.
¿Cómo instalar Passport.js y las estrategias de autenticación?
Antes de comenzar a crear estrategias, es esencial instalar las dependencias necesarias en nuestro proyecto. En la terminal, ejecutamos los siguientes comandos para instalar Passport.js y su estrategia local:
npminstall passport
npminstall passport-local
Estas instalaciones permitirán gestionar autenticaciones básicas mediante usuario y contraseña.
¿Cómo organizar el proyecto para gestionar estrategias?
Una buena práctica es tener un directorio específico para las estrategias de autenticación. Esto nos ayuda a mantener nuestro proyecto ordenado y escalable. Creamos una carpeta llamada utils, y dentro de ella, un subdirectorio llamado auth, donde colocaremos nuestras estrategias.
Aquí es donde desarrollaremos la primera estrategia de autenticación usando passport-local.
¿Cómo implementar una estrategia con Passport Local?
Para implementar nuestra estrategia local, primero debemos importar Passport y la estrategia local en nuestro index.js dentro de auth. Además, configuramos nuestra estrategia para manejar el login.
const passport =require('passport');constLocalStrategy=require('passport-local').Strategy;passport.use(newLocalStrategy(async(username, password, done)=>{// Implementación de la lógica de autenticación}));
Hemos definido una nueva LocalStrategy, pero aún debe incluir la lógica de negocio para manejar un login por usuario y contraseña. Passport nos proporciona automáticamente los parámetros username y password, así como una función done para manejar el resultado del proceso de autenticación.
¿Cómo integrar un servicio de usuarios?
Nuestra estrategia utiliza un servicio para buscar usuarios por email en la base de datos. Implementamos un método findByEmail en nuestro servicio de usuarios para realizar esta búsqueda:
constUserService=(db)=>{constfindByEmail=async(email)=>{// búsqueda en la base de datos};return{ findByEmail };};
En nuestro archivo de estrategia, necesitamos importar y usar este servicio para verificar si el usuario existe y validar su contraseña:
const userService =newUserService();passport.use(newLocalStrategy(async(email, password, done)=>{try{const user =await userService.findByEmail(email);if(!user ||!user.validPassword(password)){returndone(null,false);}returndone(null, user);}catch(error){returndone(error);}}));
¿Cómo manejar los errores y la respuesta exitosa?
En caso de que algo salga mal, Passport utiliza la función done para propagar errores. Importamos Boom para estructurar errores HTTP y enviar respuestas adecuadas:
constBoom=require('@hapi/boom');passport.use(newLocalStrategy(async(email, password, done)=>{try{const user =await userService.findByEmail(email);if(!user){returndone(Boom.unauthorized('Email o contraseña incorrecta'),false);}if(!user.validPassword(password)){returndone(Boom.unauthorized('Email o contraseña incorrecta'),false);}returndone(null, user);}catch(error){returndone(error);}}));
¿Cómo personalizar los nombres de los campos en la estrategia?
Si preferimos utilizar el campo email en lugar de username, alteramos la configuración de Passport Local antes de definir nuestra estrategia:
Finalmente, conectamos nuestra estrategia con las rutas de la API. Creamos un archivo authRouter.js en el que importamos la estrategia y definimos nuestro endpoint:
Hasta ahora, hemos logrado verificar usuarios utilizando Passport.js, pero la autenticación es solo una parte del proceso. Para mantener sesiones seguras y autorizar posteriormente acciones de usuarios, los JSON Web Tokens (JWT) son cruciales. Continuaremos con los JWT en el siguiente paso para enriquecer nuestro sistema de autenticación. ¡No te lo pierdas!
Implementación de Autenticación con Passport.js y Estrategia Local
Implementación de Autenticación con Passport.js y Estrategia Local
Recursos
Resumen
¿Cómo implementar la autenticación con Passport.js?
Passport.js es una herramienta valiosa para implementar capas de autenticación en aplicaciones web. Este middleware es flexible y admite múltiples estrategias de autenticación, como login con usuario y contraseña o a través de plataformas sociales como Facebook y Twitter. Además, Passport.js facilita la integración de nuevas estrategias sin modificar la estructura de nuestro código base.
¿Cómo instalar Passport.js y las estrategias de autenticación?
Antes de comenzar a crear estrategias, es esencial instalar las dependencias necesarias en nuestro proyecto. En la terminal, ejecutamos los siguientes comandos para instalar Passport.js y su estrategia local:
npm install passport
npm install passport-local
Estas instalaciones permitirán gestionar autenticaciones básicas mediante usuario y contraseña.
¿Cómo organizar el proyecto para gestionar estrategias?
Una buena práctica es tener un directorio específico para las estrategias de autenticación. Esto nos ayuda a mantener nuestro proyecto ordenado y escalable. Creamos una carpeta llamada utils, y dentro de ella, un subdirectorio llamado auth, donde colocaremos nuestras estrategias.
Aquí es donde desarrollaremos la primera estrategia de autenticación usando passport-local.
¿Cómo implementar una estrategia con Passport Local?
Para implementar nuestra estrategia local, primero debemos importar Passport y la estrategia local en nuestro index.js dentro de auth. Además, configuramos nuestra estrategia para manejar el login.
const passport = require('passport');
const LocalStrategy = require('passport-local').Strategy;
passport.use(new LocalStrategy(
async (username, password, done) => {
// Implementación de la lógica de autenticación
}
));
Hemos definido una nueva LocalStrategy, pero aún debe incluir la lógica de negocio para manejar un login por usuario y contraseña. Passport nos proporciona automáticamente los parámetros username y password, así como una función done para manejar el resultado del proceso de autenticación.
¿Cómo integrar un servicio de usuarios?
Nuestra estrategia utiliza un servicio para buscar usuarios por email en la base de datos. Implementamos un método findByEmail en nuestro servicio de usuarios para realizar esta búsqueda:
const UserService = (db) => {
const findByEmail = async (email) => {
// búsqueda en la base de datos
};
return { findByEmail };
};
En nuestro archivo de estrategia, necesitamos importar y usar este servicio para verificar si el usuario existe y validar su contraseña:
En caso de que algo salga mal, Passport utiliza la función done para propagar errores. Importamos Boom para estructurar errores HTTP y enviar respuestas adecuadas:
Finalmente, conectamos nuestra estrategia con las rutas de la API. Creamos un archivo authRouter.js en el que importamos la estrategia y definimos nuestro endpoint:
Hasta ahora, hemos logrado verificar usuarios utilizando Passport.js, pero la autenticación es solo una parte del proceso. Para mantener sesiones seguras y autorizar posteriormente acciones de usuarios, los JSON Web Tokens (JWT) son cruciales. Continuaremos con los JWT en el siguiente paso para enriquecer nuestro sistema de autenticación. ¡No te lo pierdas!
Passport.js es una serie de librerías y estrategias que nos brinda para hacer la capa de autenticación, permite generar varias estrategias (twitter, github, facebook, etc.) teniendo un código base para logearnos de diferentes maneras.
Página oficial
Instalando passport: npm i passport
passport-local permite hacer un login básico usando username y password, su instalación es: npm install passport-local
Se crea la carpeta utils/auth/strategies donde se colocarán las estrategias a implementar.
Se crea el archivo local.strategy.js, ahí se crea una instancia de Strategy la cual recibe una función callback con la lógica de negocio. Recibe tres parámetros: email, password y la función que se usará cuando todo salga bien o mal.
done
En este mismo archivo se crea una serie de validaciones, en primer lugar valida si existe el email y en segundo lugar valida si la password es correcta.
Para la primera validación se busca el email y si no se encuentra, se ejecuta la función done lanzando un error de tipo boom (unauthorized) con un false (no se pudo hacer la validación).
En la segunda validación ya se encontraron los datos de usuario en la base de datos, por lo tanto, verifica que la contraseña que envía el usuario sea la misma que la que se encuentra en la BD (hace una comparación). Si las contraseñas no son iguales se ejecuta la función done lanzando un error de tipo boom con un false.
Si todo es correcto se ejecuta done indicando que no hay error (null) y enviando el usuario (user).
Por defecto, la estrategia local maneja username y password, es posible cambiar esos valores por email y password mandando esas opciones antes de la función asíncrona con el uso de usernameField y passwordField.
En el archivo utils/auth/index.js se definen las estrategias a usar, cabe mencionar que puede tener muchas estrategias de autenticación. Algo muy importante es que debe implementarse en el index.js del proyecto con require('./utils/auth'):
En user.service.js se hacen las modificaciones permanentes, en este caso se crea el método findByEmail para buscar por el email que es nuestro username.
Para implementar la estrategia se crea un nuevo routing auth.router.js, cada estrategia tiene un nombre clave y debido a que funciona como un middleware, se necesita dejarlo pasar a la lógica de servicio o conectarlo a un servicio principal, pero antes hacer la validación.
En este caso se usa passport y con el método authenticate se indica cuál estrategia se va a autenticar diciéndole que no se desea manejar sesiones (session: false) ya que posteriormente se implementará JWT para manejar sesiones.
Si todo bien, entonces en el siguiente middleware se le envía un request con el usuario (req.user).
You may install all strategies as you want, some of the available strategies are:
passport-local
passport-facebook
passport-twitter
**passport-jwt**
Configure environment:
Following a good fold structure, we can follow the next structure
utils/
index.js
auth/
strategies
Utils will contain all utilities in our program, one of these is Authentication tools
Inside auth, strategies stores all strategies that will be used in our application.
Finally to configure our strategies, create anindex.js file.
Programming strategies:
Step 1: Config Strategy
Each strategy, have its own way to be programmed, in this case, local strategy is configured:
//Destructure passport-local strategy and get just Strategy
const { Strategy } = require('passport-local');
//Import service which will be used for authentication
const Service = require("./../../../services/user.service.js")
const userService = new Service();
const boom = require('@hapi/boom');
const bcrypt = require('bcrypt');
//Create a new strategy and configure it
const localStrategy = new Strategy({
usernameField: 'email'
},
async(email, password, done) => {
try {
const user = await userService.findByMail(email);
if (!user) {
//return error and it wont be authenticated
done(boom.unauthorized(), false);
return;
}
//Using bcrypt to compare the password with the hash
const isMatch = await bcrypt.compare(password, user.password);
if (!isMatch) {
done(boom.unauthorized(), isMatch);
return;
}
delete user.dataValues.password;
//Null errors and return user
done(null, user);
} catch (err) {
//Return error and false, it wont be authenticated
done(err, false);
}
});
//Export the strategy
module.exports = localStrategy;
Step 2: Config index.js
Just need to configure in this way all strategies which will be used in our application
//Require passport
const passport = require('passport');
//Require strategy used
const localStrategy = require('./strategies/local.strategy');
//Tell to passport we'll use localStrategy
passport.use(localStrategy);
Step 3: Config a route: routes/auth.router.js
const express = require('express');
const passport = require('passport');
const router = express.Router();
router.post('/login',
//We are using the local strategy and not using sessions
passport.authenticate('local', { session: false }),
async(req, res, next) => {
try {
res.json(req.user);
} catch (err) {
next(err);
}
}
);
module.exports = router;
Step 4: Make our application work with passport
In file index.js of root applications, just configure passport before initializing routes.
🌉 Clase #7: Implementando login con Passport.js 7/20 🌉
¿Qué es Passport js? 🚣
Passport contiene una serie de librerías y estrategias (más de 500 estrategias) que permite hacer la capa de autenticación. (Fuente: aquí).
¿Qué significa que Passport sea versátil? ⛵️
Passport permite logearnos mediante diferentes estrategias o formas como Twitter, Google, Facebook, etc. Lo que se hace es teniendo nuestro código base, aplicamos con Passport la estrategia con la que queremos hacer login a través del endpoint /auth/login.
Vamos a instalar la primera estrategia: passport-local que es la estrategia más básica que permite logearnos con nuestro usuario y contraseña.
Para instalar passport y passport-local vamos a la terminal y ejecutamos:
npm i passport passport-local
Ahora vamos a VSC para implementar la estrategia, creamos la carpeta utils, dentro de utils, se crea otra carpeta llamada auth, dentro de auth, creamos el archivo index.js, el código queda:
const passport = require('passport');
//Aqui se definen qué estrategias se van a usar
const LocalStrategy = require('./strategies/local.strategy');
passport.use(LocalStrategy);
Guardamos, dentro de la carpeta de auth, se crea la carpeta donde estarán todas las estrategias: strategies, dentro de strategies, creamos el archivo local.strategy.js donde se implementará la lógica del passport-local, el código queda:
//Nos treamos a passport-local
const { Strategy } = require('passport-local');
//Usamos a boom como manejador de errores
const boom = require('@hapi/boom');
const bcrypt = require('bcrypt');
const UserService = require('../../../services/user.service');
//Se crea una instancia de servicio
const service = new UserService();
//Constructor
const LocalStrategy = new Strategy({
//La estrategia local es la más básica
usernameField: 'email',
passwordField: 'password'
},
async (email, password, done) => {
try {
//Buscar con findByEmail el usuario
const user = await service.findByEmail(email);
//Validaión para cuando el usuario no exista
if (!user) {
//Así se envían los errores con false
done(boom.unauthorized(), false);
}
//Comprobación del password con el hash
const isMatch = await bcrypt.compare(password, user.password);
//Si el password no coincide, sale un mensaje que el usuario no está autorizado
if (!isMatch) {
done(boom.unauthorized(), false);
}
delete user.dataValues.password;
//Si todo salió bien, se ejecuta el done enviando null diciendo que nno hay error
done(null, user);
} catch (error) {
done(error, false);
}
}
);
module.exports = LocalStrategy;
Guardamos, vamos a la carpeta de services y abrimos el archivo de user.services.js, después de la función find(), se implementa la función findByEmail(email) que permitirá buscar el usuario por el email, el código queda:
//Encontrar el primer usuario que tenga el email qué además es único
async findByEmail(email) {
const rta = await models.User.findOne({
where: { email }
});
return rta;
}
Guardamos, vamos a la carpeta routes, creamos el archivo auth.router.js, el código queda:
Guardamos, abrimos dentro de la carpeta routes el archivo index.js, agregamos donde están las constantes al final de éstas:
const authRouter = require('./auth.router');
También agregamos la ruta dentro de routerApi():
router.use('/auth', authRouter);
Guardamos, abrimos el archivo de index.js (el index más externo) y agregamos un require dinámico para que entienda donde se debe dirigir cuando se haga el endpoint:
//require de forma dinámica para ejecutar el archivo de la estrategia
require('./utils/auth');
Guardamos, si no está corriendo en la terminal en modo de desarrollador, se ejecuta: npm run dev
Vamos a Insomnia, creamos una carpeta llamada Auth, dentro creamos la petición POST llamada Login, en la dirección se coloca: .API_URL/api/v1/auth/login, en el body del JSON, colocamos:
++Nota:++ Si se quiere editar los nombres de los campos de entrada para el Login, en el archivo local.strategy.js de la ruta: utils/auth/strategies se debe indicar los nombres autorizados:
Hay un problema con la version de passport. Si utilizan la misma version que en el curso, la 0.4.1, todo sale bien. Si utilizan la ultima version, la 0.5.0, van a tener el error TypeError: req.logIn is not a function, por lo que hay que reescribir la estrategia. Yo decidi dejarla como en el curso y utilizar la version 0.4.1, despues la actualizo
Por si a alguien le pasa lo mismo que a mi , por mas que pusiera "app.use(passport.initialize());" en el index me seguia dando el error de que la funcion authenticate no existia y que si o si tenia que nombrar la estrategia, la unica solucion que me funciono fue declarar la new strategy en el mismo index de la carpeta auth mi codigo quedo asi:
Hola a todos espero esten teniendo un excelente día!,
tengo una pregunta la cual surge del archivo local.strategy.js... ¿por que a la hora de pasar la contraseña que tenemos en la base de datos por la función compare de bcrypt, no utiliza el user.dataValues.password y en cambio utiliza solo user.password?.
.
.
Mi duda surge por que más adelante para eliminar la contraseña de la información que regresamos ustamos esta propiedad de dataValues.
.
.
Muchas gracais de antemano a todos aquellos que se tomaron la molestia de leer mi pregunta.
Para evitar que en cualquier consulta que involucre los usuarios puede indicar un getter en modelo User en el campo password que retorne siempre nulo este valor..