👾
Node.js
  • 🧑‍💻Full-Stack Web Developer
  • 📚Теорія
    • 1️⃣Основи Node.js
      • Вступ
      • Модулі Node.js
      • Запуск скриптів модулів в Node.js
      • Структура проєкту, експорт-імпорт, index.js як хаб
      • Модулі CommonJS
      • Модулі MJS
      • Модулі ECMAScript
      • Модулі NPM + базові модулі
      • Глобальні змінні
      • Робота з файлами
    • 2️⃣Консольні додатки
      • Створення консольних додатків
    • 3️⃣Фреймворк Express
      • Про Express
      • Nodemon і запуски скриптів
      • Postman
      • Проміжне ПЗ middleware
      • Передача даних на сервер
      • Роутінг
      • CRUD
      • Налаштування лінтера
    • 4️⃣REST API
      • Змінні оточення
      • Логування
      • REST
      • Методи HTTP
      • CORS
      • Формування URL для REST API
      • Контроллери відсутнього роуту і непередбачуваної помилки
      • Валідація даних Joi
      • Рефакторинг додатку за MVC архітектурою
      • Express автогенератор додатку
    • 5️⃣База даних Mongo.DB
      • Основи MongoDB
      • Налаштування Mongo Atlas
      • Встановлення локальної MongoDB і основні команди
    • 6️⃣ODM Mongoose
      • Mongoose
      • Порядок планування бекенд додатку
      • чорнетка
    • 7️⃣Автентифякація WJT
      • чорнетка
      • чорнетка 2
    • 8️⃣Файли
      • чернетка
    • 9️⃣тестування
      • чернетка
    • 🔟Page 14
      • імейли
    • чорнетка докер
    • чорнетка сокети
    • додаткові матеріали
    • 👷Практика
      • 1️⃣Page 4
      • 2️⃣Page 5
      • 3️⃣Page 6
      • 4️⃣Page 7
      • 5️⃣Page 8
      • 6️⃣Page 9
  • Про мене
    • Про мене
Powered by GitBook
On this page
  1. Теорія
  2. Автентифякація WJT

чорнетка

PreviousАвтентифякація WJTNextчорнетка 2

Last updated 1 year ago

про jwt

одразу встановимо пакет

npm i jsonwebtoken

автентифікація - перевірка наявності користувача в бд

авторизація - перевірка на доступ до бд

З самого початку в індекс файлі підключимо роут, який відповідатиме за реєстрацію та залогінення

const authRoutes = require("./routes/authRoutes");
// code
app.use("/auth", authRoutes);

Пропишемо сам роут для реєстрації та залогінення: routes/authRoutes.js

routes/authRoutes.js
const { Router } = require("express");

// контроллери і мідлвари
const authController = require("../controllers/authController");
const authMiddlewares = require("../middlewares/authMiddlewares");

// можна одразу деструктуризувати
// const { checkSignupUserData } = require("../middlewares/authMiddlewares");

const router = Router();

// signup - register new user
router.post(
  "/signup",
  authMiddlewares.checkSignupUserData,
  authController.signup
);

// login - login user - authentification
router.post("/login", authController.login);

module.exports = router;

По суті ми прописали нові роути. signup - реєструє користувача (це для самого користувача з обмеженим функціоналом (без надання права встановлення ролі)), а login - автентифікує користувача.

Middleware checkSignupUserData у файлі middlewares/authMiddlewares.js - перевірятиме вхідні дані при реєстрації користувача, наявність користувача з таким email, якщо все ок, то пропускає далі

middlewares/authMiddlewares.js
const { catchAsync, AppError, userValidator } = require("../utils");
const User = require("../models/userModel");

exports.checkSignupUserData = catchAsync(async (req, res, next) => {
  const { error, value } = userValidator.signupUserDataValidator(req.body);
  if (error) {
    console.log(error);
    throw new AppError(400, "Invalid user data..");
  }

  const userExists = await User.exists({ email: value.email });

  if (userExists) throw new AppError(409, "User with this email exists..");

  req.body = value;

  next();

});

У коді вище signupUserDataValidator - це валідація даних за допомогою Joi у файлі utils/userValidator.js. Тут всі поля на відповідність вимогам. Але при цьому role юзера не перевіряємо, бо вона буде створюватися автоматично.

utils/userValidator.js
exports.signupUserDataValidator = (data) =>
  Joi.object()
    .options({ abortEarly: false })
    .keys({
      name: Joi.string().min(3).max(30).required(),
      year: Joi.number().min(1940).max(2023),
      email: Joi.string().email().required(),
      password: Joi.string().regex(PASSWD_REGEX).required(),
    })
    .validate(data);

Тепер налаштовуємо контроллер controllers/authController.js

controllers/authController.js
const jwt = require("jsonwebtoken");
const userRolesEnum = require("../constants/userRolesEnum");
const User = require("../models/userModel");
const { catchAsync } = require("../utils");
// const userService = require("../services/userServices");

const signToken = (id) =>
  jwt.sign({ sub: id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN,
  });

exports.signup = catchAsync(async (req, res) => {
  const newUserData = {
    ...req.body,
    role: userRolesEnum.USER,
  };

  const newUser = await User.create(newUserData);
  newUser.password = undefined;

  const token = signToken(newUser.id);

  res.status(201).json({
    user: newUser,
    token,
  });

});

Тут ми використовуємо змінні JWT_SECRET і JWT_EXPIRES_IN, які ми зберігаємо у файлі .env і завантажуємо в оточення піл час запуску сервера. (одразу вписати копію в .example файл, але з пустими даними)

enviroments/development.env
JWT_SECRET = "jfdsnfvlkndlk"
JWT_EXPIRES_IN='10m'

ВСЕ ВКАЗАНЕ ВИЩЕ ІТАК ПРАЦЮЄ, АЛЕ МОДНА ПРОВЕСТИ ДЕЯКИЙ РЕФАКТОРИНГ

Тут краще signToken винести в окремий файл і не тримати його в контролерах, а такод виносити в окремий файл все що повʼязано щ моделлю в даному випалку User

створимо папку services і в ньому файл jwtService.js і в неї винесемо функцію

services/jwtService.js
const jwt = require("jsonwebtoken");

exports.signToken = (id) =>
  jwt.sign({ sub: id }, process.env.JWT_SECRET, {
    expiresIn: process.env.JWT_EXPIRES_IN,
  });

І винесемо із контролерів обробку юзера (Це бажано робити у всіх мідлварках і контролерах)

Для Цього створимо services/userServices.js

services/userServices.js
const userRolesEnum = require("../constants/userRolesEnum");
const { signToken } = require("../services/jwtService");
const User = require("../models/userModel");

exports.signupUser = async (userData) => {
  const newUserData = {
    ...userData,
    role: userRolesEnum.USER,
  };

  const newUser = await User.create(newUserData);
  newUser.password = undefined;

  const token = signToken(newUser.id);
  return { user: newUser, token };
};

І скоротимо контролер

controllers/authController.js
const { catchAsync } = require("../utils");
const userService = require("../services/userServices");

exports.signup = catchAsync(async (req, res) => {
  const { user, token } = await userService.signupUser(req.body);

  res.status(201).json({
    user,
    token,
  });
});

Це повний цикл самостійної реєстрації користувача

-------------

Тепре напишемо залогінення користувача

Контроллер

controllers/authController.js
const { catchAsync, AppError } = require("../utils");
const User = require("../models/userModel");
const bcrypt = require("bcrypt");
const { signToken } = require("../services/jwtService");

exports.login = catchAsync(async (req, res) => {
 
  const { email, password } = req.body;
  const user = await User.findOne({ email }).select("+password");
  if (!user) throw new AppError(401, "Not authorized...");

  const passwordIsValid = await bcrypt.compare(password, user.password);
  if (!passwordIsValid) throw new AppError(401, "Not authorized...");
  user.password = undefined;

  const token = signToken(user.id);

  res.status(200).json({
    user,
    token,
  });
});

цей код працює, аде уже можемо так само проветси рефакторинг коду як і з реєстрацією.

винесемо в services/userServices.js

services/userServices.js
const { signToken } = require("../services/jwtService");
const User = require("../models/userModel");
const { AppError } = require("../utils");
const bcrypt = require("bcrypt");

exports.loginUser = async (userData) => {
  const { email, password } = userData;
  const user = await User.findOne({ email }).select("+password");
  if (!user) throw new AppError(401, "Not authorized...");

  const passwordIsValid = await bcrypt.compare(password, user.password);
  if (!passwordIsValid) throw new AppError(401, "Not authorized...");
  user.password = undefined;

  const token = signToken(user.id);
  return { user, token };
};

А в контролерах лишимо оце

controllers/authController.js
const { catchAsync } = require("../utils");
const userService = require("../services/userServices");

exports.login = catchAsync(async (req, res) => {
  const { user, token } = await userService.loginUser(req.body);

  res.status(200).json({
    user,
    token,
  });
});

НА ЦЬОМУ ЕТАПІ УЖЕ Є АВТОРИЗАЦІЯ. тобто роути сайнап і логін відкриті для всіх.

Але треба провести автентифікацію, щоб звертатися по ендпойнтах для зміни в БД могли тільки авторизовані користувачі. для цього потрібно прописати мідлварку захисту тобто, щоб доступ до внесення змін в бд був тільки з токеном

Щоб в програмі Postman передавати токен потрібно вибрати autorization і bearer token

middlewares/authMiddlewares.js
exports.protect = catchAsync(async (req, res, next) => {
  const token =
    req.headers.authorization?.startsWith("Bearer") &&
    req.headers.authorization.split(" ")[1];
   // console.log(token);
 
   if (!token) throw new AppError(401, "Not logged in..");
  let decoded;
  try {
    decoded = jwt.verify(token, process.env.JWT_SECRET);
    // console.log(decoded);
  } catch (err) {
    console.log(err.message);
    throw new AppError(401, "Not logged in..");
  }
  const currentUser = await User.findById(decoded.sub);

  if (!currentUser) throw new AppError(401, "Not logged in..");

  req.user = currentUser;

  next();
});

а в роутах де є доступ до бази даних для юзера треба вставити ту мідлварку перед всіма роутами

const { protect } = require("../middlewares/authMiddlewares");

router.use(protect);

тобто в роутах буде проходити протект, а потім інші роути

На цьому етапі уже працює -------

Додамо ще один роут для авторизованиз користувачів

routes/userRoutes.js
router.get("/get-me", getMe);

в контролерах напишемо юзер контролер якщо хочемо повертати залогіненого юзера по його jwt

controllers/userController.js
/**
 * Get logged in user data.
 */
exports.getMe = (req, res) => {
  res.status(200).json({
    user: req.user,
  });
};

-----------

напишемо мідлваоку доступу по ролях (якщо у вкащаному масиві є роль яка збігається із роллю нашого користувача, то пропустить, якшо немає, то поверне далі еррорку)

middlewares/authMiddlewares.js
// /**
//  * Roles guard middleware
//  * allowFor('admin', 'moderator')
//  * use ONLY after 'protect'
//  * @param  {String} roles
//  * @returns {Function}
//  */
exports.allowFor =
  (...roles) =>
  (req, res, next) => {
    if (roles.includes(req.user.role)) return next();

    next(new AppError(403, "You are not allowed to perform this action.."));
  };

після цього деякі роути можна закрити тільки для адміна і модератора.

Після гет мі в роутах підключаємо

routes/userRoutes.js
router.use(allowFor(userRolesEnum.ADMIN, userRolesEnum.MODERATOR));

все працює, але можна порефакторити тохи

створимо в services/jwtService.js нову функцію

services/jwtService.js
// Some code

📚
7️⃣
https://jwt.io/#debugger-io
https://www.npmjs.com/package/jsonwebtoken