¿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!
En el momento de nosotros loguearnos nos lanza el siguiente error 👇🏽
message": "req.logIn is not a function",
para poder solucionar este error sin tener que bajar nuestra version de passport podemos hacer lo siguiente 👨💻
--
Index.js
const passport =require('passport')// Antes de nuestras rutas debemos de colocar estoapp.use(passport.initialize());// RutasrouterApi(app);
Esto sucede ya que debemos de inicializar el middleware de passport antes de nuestras rutas
y ahora todo deberia de funcionar con normalidad 🥳
Que crack, muchas gracias!
gran aporte bro
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 done que se usará cuando todo salga bien o mal.
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.
const{Strategy}=require('passport-local');const bcrypt =require('bcrypt');const boom =require('@hapi/boom');constUserService=require('../../../services/user.service');const service =newUserService();constLocalStrategy=newStrategy({usernameField:'email',passwordField:'password',},async(email, password, done)=>{try{const user =await service.findByEmail(email);if(!user){done(boom.unauthorized(),false);}const isMatch =await bcrypt.compare(password, user.password);if(!isMatch){done(boom.unauthorized(),false);}delete user.dataValues.password;done(null, user);}catch(error){done(error,false);}});module.exports=LocalStrategy;
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 Strategyconst{Strategy}=require('passport-local');//Import service which will be used for authenticationconstService=require("./../../../services/user.service.js")const userService =newService();const boom =require('@hapi/boom');const bcrypt =require('bcrypt');//Create a new strategy and configure itconst localStrategy =newStrategy({usernameField:'email'},async(email, password, done)=>{try{const user =await userService.findByMail(email);if(!user){//return error and it wont be authenticateddone(boom.unauthorized(),false);return;}//Using bcrypt to compare the password with the hashconst isMatch =await bcrypt.compare(password, user.password);if(!isMatch){done(boom.unauthorized(), isMatch);return;}delete user.dataValues.password;//Null errors and return userdone(null, user);}catch(err){//Return error and false, it wont be authenticateddone(err,false);}});//Export the strategymodule.exports= localStrategy;
Step 2: Config index.js
Just need to configure in this way all strategies which will be used in our application
//Require passportconst passport =require('passport');//Require strategy usedconst 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.
Gracias. Me ha servido para ver que falta añadir las opciones como primer parámetro en la inicialización de new Strategy:
newStrategy({usernameField:'email'},async(
🌉 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 usarconstLocalStrategy=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-localconst{Strategy}=require('passport-local');//Usamos a boom como manejador de erroresconst boom =require('@hapi/boom');const bcrypt =require('bcrypt');constUserService=require('../../../services/user.service');//Se crea una instancia de servicioconst service =newUserService();//ConstructorconstLocalStrategy=newStrategy({//La estrategia local es la más básicausernameField:'email',passwordField:'password'},async(email, password, done)=>{try{//Buscar con findByEmail el usuarioconst user =await service.findByEmail(email);//Validaión para cuando el usuario no existaif(!user){//Así se envían los errores con falsedone(boom.unauthorized(),false);}//Comprobación del password con el hashconst isMatch =await bcrypt.compare(password, user.password);//Si el password no coincide, sale un mensaje que el usuario no está autorizadoif(!isMatch){done(boom.unauthorized(),false);}delete user.dataValues.password;//Si todo salió bien, se ejecuta el done enviando null diciendo que nno hay errordone(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 únicoasyncfindByEmail(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:
const express =require('express');const passport =require('passport');//No se necesita validar con el schema//const validatorHandler = require('./../middlewares/validator.handler');//const { loginAuthSchema } = require('./../schemas/user.schema');const router = express.Router();//Funciona como un middlewarerouter.post('/login', passport.authenticate('local',{session:false}),async(req, res, next)=>{try{//Pasa el usuario res.json(req.user);}catch(error){next(error);}});/*router.post(
"/login",
validatorHandler(loginAuthSchema, "body"),
passport.authenticate("local", { session: false }),
async (req, res, next) => {
try {
res.json(req.user);
} catch (err) {
next(err);
}
}
);*/module.exports= router;
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 estrategiarequire('./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:
usernameField:'email',passwordField:'password'
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
Hice lo mismo que tu jaja
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:
const boom =require('@hapi/boom');const bcrypt =require('bcrypt');const passport =require('passport')constLocalStrategy=require('passport-local').Strategy;constUserService=require('../../services/user.service')const service =newUserService()passport.use(newLocalStrategy({usernameField:'email',passwordField:'password'},async(email, password, done)=>{try{const user =await service.findByEmail(email)if(!user){done(boom.unauthorized(),false)}const isMatch =await bcrypt.compare(password, user.password)if(!isMatch){done(boom.unauthorized(),false)}delete user.dataValues.passworddone(null, user)}catch(error){done(error,false)}}))
Gracias por tu aporte, a mi si me funciono también así, y estoy utilizando la ultima versión instalada de passport "^0.5.2"
También se puede exportar desde utils/auth/index.js y usas esa instancia de passport en la ruta
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.
Aqui les dejo un aporte de como implementar el login con username o con el correo.
En LocalStrategy
constLocalStrategy=newStrategy({usernameField:'identifier',passwordField:'password',},async(identifier, password, done)=>{try{const user =await service.userLogin(identifier);if(!user){const user =await service.emailLogin(identifier);if(!user){done(boom.unauthorized('User not found'));}else{const validatePassword =await bcrypt.compare(password, user.user.password);if(!validatePassword){done(boom.unauthorized('Invalid Password'),false);}else{delete user.user.dataValues.password;done(null, user);}}}else{const validatePassword =await bcrypt.compare(password, user.password);if(!validatePassword){done(boom.unauthorized('Invalid Password'),false);}else{delete user.dataValues.password;done(null, user);}}}catch(error){done(error,false);}},);
En el service de users cree dos metodos para buscar por username o por correo
Debo mencionar que las relaciones entre user y customer lo manejo diferente al profe
Me arroja "Bad Request", no se como solucionarlo :/
Puede deberse a que no le estas pasando los valores que espera new Strategy()
Hola, a mi me pasó igual. Los campos de entrada que coloqué en el JSON para hacer la petición del POST en Insomnia no eran los correctos, en lugar de usar "username" como lo hace el profesor en la clase, usé "email":
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..
En lo personal elijo mantener el esquema de validación.
Lo coloque antes de la authenticacion, ya que, si hay un error con los datos enviados, passport solo nos lanzara un "Bad request", en cambio con el esquema podemos mostrar cuales son los datos que no están siendo enviados correctamente en el "body" de la petición.
{"statusCode":400,"error":"Bad Request","message":"\"password\" is required"}
Sin esquema:
BadRequest
npm i passport passport-local
en el video le falta los return en el LocalStrategy, pero en los aportes de la clase si están!
returndone(Boom.unauthorized('Email o contraseña incorrecta'),false);
sin el return el codigo seguiria causando errores
No seria una buena practica mejor validar directamente en el servicio sino existe el usuario y mandar directamente ahí la excepción?
Tenia la misma duda pero ChatGPT me ayudó:
La verificación de credenciales (comparar contraseña, validar email, lanzar errores, etc.) debe hacerse dentro del Strategy, no en el servicio.
🔍 Razón técnica
El propósito del service (por ejemplo, UserService) es ser una capa de acceso a datos.
Su responsabilidad es buscar, crear o modificar usuarios, no decidir si alguien puede autenticarse.
Por eso:
El service puede tener un método como findByEmail(email) que simplemente devuelva el usuario o null.
Pero no debe lanzar un error boom si el usuario no existe, porque esa decisión depende del contexto en el que se use (login, registro, cambio de contraseña, etc.).
🧠 En cambio, el Strategy sí debe hacerlo
El Strategy (por ejemplo, LocalStrategy) es la capa de negocio de autenticación.
Su función es verificar las credenciales y decidir si:
se autentica correctamente, o
se lanza un error (por ejemplo, boom.unauthorized() o boom.notFound()).
Si les aparece el siguiente error:
TypeError: Cannot read properties of undefined (reading 'name')
Eliminado los corchetes, dejando el import de la siguiente manera
Hola developers! Estoy intentando solucionar este error:
{
"message": "The server does not support SSL connections",
"stack": "SequelizeConnectionError: The server does not support SSL connections\n at Client._connectionCallback (C:\Users\tonib\dev\courses\platform\Platzi\onlineStoreProject\authPassportJsJWTPart\node_modules\sequelize\lib\dialects\postgres\connection-manager.js:146:20)\n at Client._handleErrorWhileConnecting (C:\Users\tonib\dev\courses\platform\Platzi\onlineStoreProject\authPassportJsJWTPart\node_modules\pg\lib\client.js:305:19)\n at Client._handleErrorEvent (C:\Users\tonib\dev\courses\platform\Platzi\onlineStoreProject\authPassportJsJWTPart\node_modules\pg\lib\client.js:315:19)\n at Connection.emit (node:events:390:28)\n at Socket.<anonymous> (C:\Users\tonib\dev\courses\platform\Platzi\onlineStoreProject\authPassportJsJWTPart\node_modules\pg\lib\connection.js:71:23)\n at Object.onceWrapper (node:events:510:26)\n at Socket.emit (node:events:390:28)\n at addChunk (node:internal/streams/readable:324:12)\n at readableAddChunk (node:internal/streams/readable:297:9)\n at Socket.Readable.push (node:internal/streams/readable:234:10)"
}
¿Alguien sabe como puedo solucionarlo para poder continuar con las clases?
La verdad es que es bastante frustrante porque no sé de donde viene y me está impidiendo hacer cualquier petición, gracias de antemano!
¡Hola! :D
¿Cuál es el problema que tienes?, ¿en qué te puedo ayudar? Compártenos tu código por favor. Puedes adjuntar imágenes arrastrándolas a esta ventana de comentario.
Nunca pares de aprender 💚
me sale una y otra vez bad request y no se como solucionarlo:
este es el codigo que estoy usando:
Passport es una librería que proporciona un conjunto de estrategias para la autenticación. Aunque podríamos crear nuestros propios métodos de inicio de sesión, Passport ofrece múltiples maneras de hacerlo, permitiéndonos usar autenticaciones a través de GitHub, Google, LinkedIn, y métodos locales, entre otros.
En este caso, vamos a usar la estrategia local de Passport, que funciona con el tradicional nombre de usuario y contraseña.
Para instalarlo :
//Para hacer uso de cualquier estrategia de passport debemos instalar passport y seguido de la estrategia que queramos usar 
npm i passport passport-local
Para empezarlo a usar
// Nos traemos el módulo passport-local que es un objeto con diferentes propiedades a usar.// Utilizamos la desestructuración para obtener únicamente la propiedad {Strategy} del módulo passport-local.const{Strategy}=require("passport-local")    // Ahora creamos una instancia de strategy dado que es un constructor const localStrategy =newStrategy()    // Exportamos la variable instanciadamodule.exports= localStrategy
Ahora podemos empezar a usar el local passport
// Para usar la instancia tenemos 3 parámetros username, password , done donde "done" funcionara para las respuestas correctas y para los errores// "A través de done(), proporcionamos los siguientes parámetros: `done(error, user, info)`. Primero, el parámetro 'error' se establece en `null` si la autenticación fue exitosa o se pasa el error específico en caso de fallo. Para el parámetro 'user', se proporciona `false` si la autenticación falla, indicando que no se encontró el usuario, o se envía la información del usuario si la autenticación es correcta. El parámetro opcional 'info' se utiliza para incluir detalles adicionales, como mensajes de error específicos, como 'nombre de usuario incorrecto'."const localStrategy =newStrategy(async(email, password, done)=>{try{const user= service.findByEmail(email)// Digamos que falla la verificación le pasamos el error en este caso no autorizado y le decimos que la validación fallo if(!user){done(boom.unauthorized(),false)}// Si salio todo bien debemos devolver null y el usuario en cuestióndone(null, user)}catch(error){done(error,false)}})
Listo sabiendo lo anterior si queremos usarlo podemos utilizar la siguiente lógica
// Traemos a todo el modulo de passport const passport =require("passport")// Traemos la instacia de estrategia previamente hecha const loca\_strategy =require("./strategies/loca\_strategy")// Le decimos a passport la estrategia a usar la ventaja de trabajar de este modo es que si quisiéramos usar otras estrategias simplemente le tendriamos que pasar la instacia a passport.use() y funcionariapassport.use(loca\_strategy)
## ¿Qué es Passport?
Passport es una librería que proporciona un conjunto de estrategias para la autenticación. Aunque podríamos crear nuestros propios métodos de inicio de sesión, Passport ofrece múltiples maneras de hacerlo, permitiéndonos usar autenticaciones a través de GitHub, Google, LinkedIn, y métodos locales, entre otros.
En este caso, vamos a usar la estrategia local de Passport, que funciona con el tradicional nombre de usuario y contraseña.
Para instalarlo :
//Para hacer uso de cualquier estrategia de passport debemos instalar Passport y seguido de la estrategia que queramos usar 
npm i passport passport-local
Para empezarlo a usar
// Nos traemos el módulo passport-local que es un objeto con diferentes propiedades a usar.// Utilizamos la desestructuración para obtener únicamente la propiedad {Strategy} del módulo passport-local.const{Strategy}=require("passport-local")    // Ahora creamos una instancia de strategy dado que es un contructor const localStrategy =newStrategy()    // Exportamos la varible instaciadamodule.exports= localStrategy