Налаштування локальної та соціальної автентифікації за допомогою passport.js

pic

Статті цієї серії

Щоб зробити статті короткими та лаконічними, ми розділяємо цю тему на серію з трьох частин.

  1. Налаштування nodeJS з TypeScript
  2. Налаштування MongoDB
  3. Налаштування локальної та соціальної автентифікації за допомогою passport.js

Passport.js — це потужне та гнучке middleware для автентифікації для Node.js. Воно спрощує процес додавання автентифікації користувачів у ваші веб-застосунки.

Переваги passport.js:

  • Зменшення часу розробки: Спрощує реалізацію автентифікації.
  • Покращена безпека: Використовує найкращі практики для забезпечення безпечної автентифікації.
  • Покращений досвід користувача: Забезпечує плавні та послідовні потоки автентифікації.

Отже, давайте почнемо з установки passport. Для цього посібника ми реалізуємо локальну автентифікацію з passport. Також у цій статті ми розглянемо, як обгортати запити.

Отже, почнемо з інсталяції. Наступна команда встановить passport та інші пов’язані плагіни, необхідні для налаштування автентифікації.

npm install passport passport-local express-session body-parser connect-mongo passport-google-oauth20 bcrypt

Тепер давайте ініціалізуємо його. Перед ініціалізацією ми повинні визначити стратегію, яку ми будемо використовувати для входу. Тут ми створюємо 2 стратегії: локальну та Google. Інші доступні стратегії можна знайти тут
https://www.passportjs.org/packages/

Тому ми створимо контекстну модель користувача, яка буде використовуватись у всіх резолверах. Створіть файл /graphql/modals/i-user.ts

export interface IUser {  
 id: string,  
 name: string,  
 email: string  
}

Тепер ми створимо допоміжний файл, який ми будемо використовувати в усіх стратегіях.
Створіть файл /login_strategy/helpers.ts

import MongoStore from "connect-mongo";  
import { IUser } from "../graphql/modals/i-user";  
import config from './../loadConfig.js';  

export const passportSerializer = (user: IUser, cb) => {  
 process.nextTick(function() {  
 return cb(null, {  
 id: user.id,  
 name: user.name,  
 email: user.email  
 });  
 });  
 }  


 export const passportDeserializer = (user: IUser, cb) => {  
 process.nextTick(function() {  
 return cb(null, user);  
 });  
 }  

 export const sessionConfig = {  
 secret: 'keyboard cat',  
 resave: false,  
 saveUninitialized: false,  
 cookie: { secure: false },  
 store: MongoStore.create({  
 mongoUrl: config.db.mongo.url,  
 collectionName: "sessions",  
 stringify: false,  
 autoRemove: "interval",  
 autoRemoveInterval: 1  
 })   
}

Тепер час створювати стратегії.

/login_strategy/local.ts

import { Strategy as LocalStrategy } from 'passport-local';  
import { User } from '../repository/mongoDb/user';  
import bcrypt from 'bcrypt';  
import { v4 as uuid } from 'uuid';  

const strategy = new LocalStrategy(  
 {  
 usernameField: 'email',  
 passwordField: 'password',  
 },  
 async (email, password, done) => {  
 const user = await User.find({ email: email }).findOne();  
 if (!user) return done(null, false);  
 const match = await bcrypt.compare(password, user.password);  
 if (!match) return done(null, false);  
 return done(null, user);  
 }  
)  

export const createNewUser = async (req, res) => {  
 // Отримання даних з тіла запиту  
 const { name, email, password } = req.body;  

 // Базова валідація вводу (вам слід додати більш надійні перевірки)  
 if (!name || !email || !password) {  
 return res.status(400).json({ error: 'Відсутні обов’язкові поля' });  
 }  

 const hashedPassword = bcrypt.hashSync(password, 10);  

 const newUser = new User({  
 id: uuid(),  
 name: name,  
 email: email,  
 password: hashedPassword,  
 provider: 'local',  
 providerId: ''  
 });  

 await newUser.save();  

 res.status(201).json({ message: 'Користувача створено успішно', status: true });  
}  

export default strategy;

/login_strategy/google.ts

import { Strategy } from "passport-google-oauth20";  
import { v4 as uuid } from 'uuid';  
import { User } from "../repository/mongoDb/user";  
import config from '../loadConfig.js';  

const strategy = new Strategy({  
 clientID: config.auth.google.GOOGLE_CLIENT_ID,  
 clientSecret: config.auth.google.GOOGLE_CLIENT_SECRET,  
 callbackURL: config.auth.google.callbackURL,  
 },  
 async function verify(accessToken, refreshToken, profile, cb) {  
 console.log('inside stragey', accessToken, refreshToken, profile)  
 const user = await User.find({ providerId: profile.id}).findOne();  
 if (user && user.id) {  
 return cb(null, user);  
 } else {  
 const newUser = new User({  
 id: uuid(),  
 name: profile.displayName,  
 email: profile.emails?.[0]?.value,  
 password: uuid(),  
 provider: 'google',  
 providerId: profile.id  
 });  
 const user = await newUser.save();  
 return cb(null, user)  
 }  
 }  
);  

export default strategy;

Щоб інтегрувати Google Auth, вам потрібно створити OAuth клієнта і отримати облікові дані. Оновіть облікові дані в конфігураційному файлі. Для URL зворотного виклику ви можете встановити будь-який URL, куди Google буде перенаправляти після успішної автентифікації. Для цього посібника ми даємо http://127.0.0.1:4000/auth/google/callback

Скористайтесь посиланням нижче для отримання додаткової інформації.

[

Інтеграція Google Sign-In у ваш веб-додаток | Автентифікація | Google для розробників

Попередження: Бібліотека Google Sign-In за бажанням використовує FedCM API, і їх використання стане обов'язковим.

Зараз, коли ми створили всі необхідні файли, давайте з’єднаємо їх у index.ts

import { ApolloServer } from '@apollo/server';  
import { expressMiddleware } from '@apollo/server/express4';  
import mongoose from 'mongoose';  
import schema from './graphql/schema.js';  
import express from 'express';  
import cors from 'cors';  
import session from 'express-session';  
import passport from 'passport';  
import { IUser } from './graphql/modals/i-user.js';  
import localStrategy, { createNewUser } from './login_strategy/local.js';  
import googleStrategy from './login_strategy/google.js';  
import http from 'http';  
import bodyParser from 'body-parser';  
import { GraphQLError } from 'graphql';  
import config from './loadConfig.js';  
import { passportSerializer, passportDeserializer, sessionConfig } from './login_strategy/helpers.js';  
import { GraphqlContext } from './graphql/modals/graphql_context.js';  

declare global {  
 namespace Express {  
 interface User extends IUser {}  
 }  
}  

mongoose  
 .connect(config.db.mongo.url, {})  
 .then(() => {  
 console.log(`Db Connected`);  
 })  
 .catch(err => {  
 console.log(err.message);  
 });  

// Потрібна логіка для інтеграції з Express  
// налаштуємо сесію та супутні параметри  
const app = express();  
const httpServer = http.createServer(app);  
app.use(express.urlencoded({ extended: false }));  
app.use(session(sessionConfig));  
passport.serializeUser(passportSerializer);  
passport.deserializeUser(passportDeserializer);  


//Визначаємо стратегії входу  
passport.use(localStrategy);  
passport.use(googleStrategy);  
passport.use(microsoftStrategy);  


app.use(passport.initialize());  
app.use(passport.session());  

// Та сама ініціалізація ApolloServer, як і раніше, плюс плагін для дрена  
// для нашого httpServer.  
const server = new ApolloServer({  
 schema,  
});  

await server.start();  

// Маршрути для кожного сценарію входу  
// локальна автентифікація  
app.post('/login',   
 passport.authenticate('local', { failureRedirect: '/loginFailure' }),  
 function(req, res) {  
 res.json({message:"Success", user: req.user });  
 }  
);  
app.post('/signup', createNewUser);  

// автентифікація через Google  
app.get('/auth/google',  
 passport.authenticate('google', { scope: ['profile', 'email'] }));  

app.get('/auth/google/callback',  
 passport.authenticate('google', { failureRedirect: '/loginFailure' }),  
 function(req, res) {  
 // Успішна автентифікація, перенаправляємо на головну сторінку.  
 console.log(req.session, req.user)  
 res.redirect('/');  
 }  
);  



// Налаштуємо наш Express middleware для обробки CORS, парсингу тіла,  
// і нашої функції expressMiddleware.  
app.use(  
 '/',  
 cors(),  
 bodyParser.json({}),  
 expressMiddleware(server, {  
 context: async ({ req, res }) => {  
 // ми також могли б перевіряти ролі/дозволи користувачів тут.  
 // Це зробить весь graphql автентифікованим.  
 }  
 })  
);

// Якщо ви не хочете цього, ви можете повністю видалити цей  
 // middleware  
 const user = req.user;  

 if (!user || !user.id) {  
 // Генерація `GraphQLError` дозволяє нам вказати HTTP статус-код,  
 // стандартні `Error` будуть мати статус 500 за замовчуванням  
 throw new GraphQLError('Користувач не автентифікований', {  
 extensions: {  
 code: 'UNAUTHENTICATED',  
 http: { status: 401 },  
 },  
 });  
 }  
 return { user }  
 },  
 }),  
);  

const PORT = config.port || 4000;  
await new Promise((resolve) => httpServer.listen({ port: PORT }));

Якщо ми запустимо застосунок зараз, ми отримаємо наступні кінцеві точки, які доступні для форми даних (використовується для автентифікації) та завершеної автентифікації GraphQL.

Ось кілька прикладів запитів для створення та входу через локальну автентифікацію.

Створити локального користувача  
==================  
POST /signup  
Content-Type: application/x-www-form-urlencoded  

Тіло  
email: "[email protected]"  
password: "password"  
name: "Noble"  



Вхід як локальний користувач  
===================  
POST /login  
Content-Type: application/x-www-form-urlencoded  

Тіло  
email: "[email protected]"  
password: "password"  


Вхід через Google / Реєстрація  
GET /auth/google

Повний код доступний в репозиторії

https://github.com/rock-o/nodejs-graphql-starter

Перекладено з: Configuring local and social authentication using passsport.js

Leave a Reply

Your email address will not be published. Required fields are marked *