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

pic

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

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

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

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

Переваги passport.js включають:

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

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

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

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

Далі налаштуємо його. Перш ніж ініціалізувати, ми повинні визначити стратегію, яку будемо використовувати для входу. Тут ми створюємо дві стратегії: локальну та 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 автентифікацію, потрібно створити клієнт OAuth і отримати облікові дані. Оновіть облікові дані в конфігураційному файлі. Для URL зворотного виклику ви можете встановити будь-який URL, на який Google буде перенаправляти після успішної автентифікації. Для цього навчального посібника ми вказуємо http://127.0.0.1:4000/auth/google/callback

Використовуйте наступне посилання для додаткової інформації.

[

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

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

Проведемо модифікацію резолвера користувача, щоб він повертав поточного авторизованого користувача.

import { User } from "../../repository/mongoDb/user.js";  
import { Resolvers } from "../modals/generated/user.js";  
import { v4 as uuid } from 'uuid';  

export const resolvers: Resolvers = {  
 Query: {  
 currentUser: (parent, args, context) => {  
 // ми будемо інжектити контекст з інформацією про поточного користувача   
 // щоб усі функції могли використовувати поточну інформацію про логін  

 return context.user;  
 },  
 users: async (parent, args, context) => {  
 return await User.find({});  
 },  
 },  
 // Ми можемо прибрати мутацію тут, оскільки реєстрацію ми робимо не через   
 // GraphQL, оскільки ми робимо автентифікацію через GraphQL.   
}

Тепер, коли ми створили всі необхідні файли, давайте з’єднаємо все це в 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:"Успішно", 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 для обробки CORS, парсингу тіла,  
// і нашу функцію expressMiddleware.  
app.use(  
 '/',  
 cors(),  
 bodyParser.json({}),  
 expressMiddleware(server, {  
 context: async ({ req, res }) => {  
 // ми також можемо перевірити ролі/дозволи користувачів тут.  
 // Це зробить увесь GraphQL аутентифікованим.
// Якщо ви не хочете цього, ви можете повністю видалити цей   
 // проміжний шар  
 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 }));

Якщо ми запустимо додаток зараз, ми отримаємо наступні кінцеві точки, які будуть доступні через form-data (використовується для автентифікації) і повністю аутентифікований 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 *