чорнетка
про jwt
одразу встановимо пакет
https://www.npmjs.com/package/jsonwebtoken
npm i jsonwebtoken
автентифікація - перевірка наявності користувача в бд
авторизація - перевірка на доступ до бд
З самого початку в індекс файлі підключимо роут, який відповідатиме за реєстрацію та залогінення
const authRoutes = require("./routes/authRoutes");
// code
app.use("/auth", authRoutes);
Пропишемо сам роут для реєстрації та залогінення: 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, якщо все ок, то пропускає далі
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 юзера не перевіряємо, бо вона буде створюватися автоматично.
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
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 файл, але з пустими даними)
JWT_SECRET = "jfdsnfvlkndlk"
JWT_EXPIRES_IN='10m'
ВСЕ ВКАЗАНЕ ВИЩЕ ІТАК ПРАЦЮЄ, АЛЕ МОДНА ПРОВЕСТИ ДЕЯКИЙ РЕФАКТОРИНГ
Тут краще signToken винести в окремий файл і не тримати його в контролерах, а такод виносити в окремий файл все що повʼязано щ моделлю в даному випалку User
створимо папку 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
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 };
};
І скоротимо контролер
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,
});
});
Це повний цикл самостійної реєстрації користувача
-------------
Тепре напишемо залогінення користувача
Контроллер
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
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 };
};
А в контролерах лишимо оце
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
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);
тобто в роутах буде проходити протект, а потім інші роути
На цьому етапі уже працює -------
Додамо ще один роут для авторизованиз користувачів
router.get("/get-me", getMe);
в контролерах напишемо юзер контролер якщо хочемо повертати залогіненого юзера по його jwt
/**
* Get logged in user data.
*/
exports.getMe = (req, res) => {
res.status(200).json({
user: req.user,
});
};
-----------
напишемо мідлваоку доступу по ролях (якщо у вкащаному масиві є роль яка збігається із роллю нашого користувача, то пропустить, якшо немає, то поверне далі еррорку)
// /**
// * 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.."));
};
після цього деякі роути можна закрити тільки для адміна і модератора.
Після гет мі в роутах підключаємо
router.use(allowFor(userRolesEnum.ADMIN, userRolesEnum.MODERATOR));
все працює, але можна порефакторити тохи
створимо в services/jwtService.js нову функцію
// Some code
Last updated