Автентифікація та авторизація є ключовими елементами в галузі комп’ютерної безпеки. За допомогою ваших облікових даних, таких як логін та пароль, ви підтверджуєте свою особу, ідентифікуєте себе як зареєстрованого користувача, а потім отримуєте певні дозволи.
Аналогічно відбувається процес входу до онлайн-сервісів через облікові записи Facebook чи Google.
В цьому посібнику ми створимо Node.js API з системою автентифікації, що використовує JWT (JSON Web Tokens). Ось інструменти, які нам знадобляться:
- Express.js
- База даних MongoDB
- Mongoose
- Dotenv
- Bcrypt.js
- Jsonwebtoken
Автентифікація vs Авторизація
Що таке автентифікація?
Автентифікація – це процес встановлення особи користувача шляхом перевірки наданих облікових даних, наприклад, електронної адреси, пароля чи токенів. Ці дані порівнюються з інформацією, що зберігається про користувача в локальній системі або базі даних. У випадку їхнього збігу, процес автентифікації завершується, і користувачу надається доступ до ресурсів.
Що таке авторизація?
Авторизація відбувається після успішної автентифікації. Вона визначає, до яких саме ресурсів системи або веб-сайту користувач має доступ. У нашому випадку авторизовані користувачі зможуть переглядати дані профілів. Якщо користувач не увійшов до системи, доступ до таких даних буде обмежений.
Яскравим прикладом авторизації є соціальні мережі, наприклад Facebook чи Twitter. Без власного облікового запису ви не зможете переглядати їхній контент.
Іншим прикладом є платний контент за підпискою. Ви можете зайти на сайт, але не отримаєте доступу до контенту, доки не оформите підписку.
Попередні вимоги
Перед тим як продовжити, переконайтеся, що ви володієте базовими знаннями Javascript та MongoDB, а також маєте досвід роботи з Node.js.
Також, на вашому комп’ютері повинні бути встановлені Node та npm. Для перевірки версій запустіть команди `node -v` та `npm -v` у командному рядку. Результат повинен бути схожим на наведений нижче.
Ваші версії можуть відрізнятись. NPM автоматично встановлюється разом з Node. Якщо у вас ще немає Node, завантажте його з офіційного сайту NodeJS.
Для написання коду вам знадобиться IDE (інтегроване середовище розробки). В цьому посібнику я використовую VS Code. Якщо у вас є інший редактор, ви можете використовувати його. Якщо IDE ще не встановлено, завантажте VS Code з сайту Visual Studio, вибравши відповідну версію для вашої системи.
Налаштування проекту
Створіть папку з назвою `nodeapi` в будь-якому місці на вашому комп’ютері та відкрийте її за допомогою VS Code. Відкрийте термінал в VS Code та ініціалізуйте менеджер пакетів npm, ввівши команду:
npm init -y
Переконайтесь, що ви знаходитесь в директорії `nodeapi`.
Ця команда створить файл `package.json`, що містить інформацію про залежності, які будуть використані у проєкті.
Тепер встановимо необхідні пакети. Введіть наступну команду в терміналі:
npm install express dotenv jsonwebtoken mongoose bcryptjs
Після цього структура ваших файлів та папок повинна виглядати наступним чином:
Створення сервера та підключення бази даних
Створіть файл `index.js` та папку `config`. У папці `config` створіть два файли: `conn.js` для з’єднання з базою даних та `config.env` для зберігання змінних середовища. Скопіюйте наступний код у відповідні файли.
index.js
const express = require('express'); const dotenv = require('dotenv'); //Конфігурація dotenv перед іншими файлами dotenv.config({path:'./config/config.env'}); //Створення екземпляра express const app = express(); //Для обробки JSON даних app.use(express.json()); //Запуск сервера app.listen(process.env.PORT,()=> { console.log(`Сервер запущено на порті ${process.env.PORT}`); })
Якщо використовуєте dotenv, налаштуйте його у `index.js` до виклику інших файлів, що використовують змінні середовища.
conn.js
const mongoose = require('mongoose'); mongoose.connect(process.env.URI, { useNewUrlParser: true, useUnifiedTopology: true }) .then((data) => { console.log(`З'єднано з базою даних: ${data.connection.host}`) })
config.env
URI = 'mongodb+srv://ghulamrabbani883:[email protected]/?retryWrites=true&w=majority' PORT = 5000
Я використовую Mongo-DB Atlas URI, ви можете використовувати локальний хост.
Створення моделей та маршрутів
Модель – це структура ваших даних у базі даних MongoDB, яка зберігається у форматі JSON. Для створення моделей ми будемо використовувати схему mongoose.
Маршрутизація визначає, як програма обробляє запити від клієнта. Ми скористаємось Express Router для створення маршрутів.
Методи маршрутизації зазвичай приймають два аргументи. Перший – шлях, другий – функція, що обробляє запит клієнта. Також, можна використовувати третій аргумент – проміжне ПЗ, наприклад, для автентифікації. Оскільки ми розробляємо API з автентифікацією, ми будемо використовувати проміжне ПЗ для авторизації та автентифікації користувачів.
Створіть папки `routes` та `models`. В папці `routes` створіть файл `userRoute.js`, а в папці `models` – `userModel.js`. Заповніть файли наступним кодом.
userModel.js
const mongoose = require('mongoose'); //Створення схеми const userSchema = new mongoose.Schema({ name: { type:String, required:true, minLength:[4,'Ім\'я має містити мінімум 4 символи'] }, email:{ type:String, required:true, unique:true, }, password:{ type:String, required:true, minLength:[8,'Пароль має містити мінімум 8 символів'] }, token:{ type:String } }) //Створення моделі const userModel = mongoose.model('user',userSchema); module.exports = userModel;
userRoute.js
const express = require('express'); //Створення роутера express const route = express.Router(); //Імпорт userModel const userModel = require('../models/userModel'); //Створення маршруту реєстрації route.post('/register',(req,res)=>{ }) //Створення маршруту логіну route.post('/login',(req,res)=>{ }) //Створення маршруту для отримання даних користувача route.get('/user',(req,res)=>{ })
Реалізація функціоналу маршрутів та створення JWT токенів
Що таке JWT?
JSON Web Tokens (JWT) – це JavaScript бібліотека для створення та перевірки токенів. Це відкритий стандарт, що використовується для обміну інформацією між клієнтом та сервером. Ми будемо використовувати дві функції JWT: `sign` для генерації нового токена та `verify` для перевірки.
Що таке bcrypt.js?
Bcrypt.js – це функція хешування, розроблена Нільсом Провосом та Девідом Мазьєром. Вона використовує хеш-алгоритм для шифрування паролів. Ми будемо використовувати дві функції: `hash` для генерації хеш-значення та `compare` для порівняння паролів.
Реалізація функціоналу маршрутів
Функція зворотного виклику у маршруті приймає три аргументи: запит, відповідь та функцію `next`. Аргумент `next` не є обов’язковим, і використовується, коли потрібно передати управління наступній функції. Тепер оновіть файли `userRoute.js`, `config.env` та `index.js` наступним кодом.
userRoute.js
//Імпорт необхідних файлів та бібліотек const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); //Створення роутера express const route = express.Router(); //Імпорт userModel const userModel = require('../models/userModel'); //Створення маршруту реєстрації route.post("/register", async (req, res) => { try { const { name, email, password } = req.body; //Перевірка на пусті поля if (!name || !email || !password) { return res.json({ message: 'Будь ласка, введіть всі дані' }) } //Перевірка, чи існує користувач const userExist = await userModel.findOne({ email: req.body.email }); if (userExist) { return res.json({ message: 'Користувач з такою електронною адресою вже існує' }) } //Хешування пароля const salt = await bcrypt.genSalt(10); const hashPassword = await bcrypt.hash(req.body.password, salt); req.body.password = hashPassword; const user = new userModel(req.body); await user.save(); const token = await jwt.sign({ id: user._id }, process.env.SECRET_KEY, { expiresIn: process.env.JWT_EXPIRE, }); return res.cookie({ 'token': token }).json({ success: true, message: 'Користувача успішно зареєстровано', data: user }) } catch (error) { return res.json({ error: error }); } }) //Створення маршруту логіну route.post('/login', async (req, res) => { try { const { email, password } = req.body; //Перевірка на пусті поля if (!email || !password) { return res.json({ message: 'Будь ласка, введіть всі дані' }) } //Перевірка, чи існує користувач const userExist = await userModel.findOne({email:req.body.email}); if(!userExist){ return res.json({message:'Невірні дані для входу'}) } //Перевірка відповідності пароля const isPasswordMatched = await bcrypt.compare(password,userExist.password); if(!isPasswordMatched){ return res.json({message:'Невірний пароль'}); } const token = await jwt.sign({ id: userExist._id }, process.env.SECRET_KEY, { expiresIn: process.env.JWT_EXPIRE, }); return res.cookie({"token":token}).json({success:true,message:'Успішно увійшли в систему'}) } catch (error) { return res.json({ error: error }); } }) //Створення маршруту для отримання даних користувача route.get('/user', async (req, res) => { try { const user = await userModel.find(); if(!user){ return res.json({message:'Користувача не знайдено'}) } return res.json({user:user}) } catch (error) { return res.json({ error: error }); } }) module.exports = route;
Використовуйте блок `try-catch` при використанні `async`, інакше отримаєте помилку про відхилення промісу.
config.env
URI = 'mongodb+srv://ghulamrabbani883:[email protected]/?retryWrites=true&w=majority' PORT = 5000 SECRET_KEY = KGGK>HKHVHJVKBKJKJBKBKHKBMKHB JWT_EXPIRE = 2d
index.js
const express = require('express'); const dotenv = require('dotenv'); //Конфігурація dotenv перед іншими файлами dotenv.config({path:'./config/config.env'}); require('./config/conn'); //Створення екземпляра express const app = express(); const route = require('./routes/userRoute'); //Для обробки JSON даних app.use(express.json()); //Використання маршрутів app.use('/api', route); //Запуск сервера app.listen(process.env.PORT,()=> { console.log(`Сервер запущено на порті ${process.env.PORT}`); })
Створення проміжного ПЗ для автентифікації користувача
Що таке проміжне ПЗ?
Проміжне ПЗ – це функція, що має доступ до об’єкта запиту, відповіді та функції `next` в циклі запит-відповідь. Функція `next` викликається після завершення виконання функції. Як згадувалось раніше, використовуйте `next()`, коли потрібно виконати іншу функцію зворотного виклику або проміжне ПЗ.
Створіть папку `middleware`, а в ній файл `auth.js` і вставте туди код:
auth.js
const userModel = require('../models/userModel'); const jwt = require('jsonwebtoken'); const isAuthenticated = async (req,res,next) => { try { const {token} = req.cookies; if(!token){ return next('Будь ласка, авторизуйтесь для доступу до даних'); } const verify = await jwt.verify(token, process.env.SECRET_KEY); req.user = await userModel.findById(verify.id); next(); } catch (error) { return next(error); } } module.exports = isAuthenticated;
Тепер встановіть бібліотеку `cookie-parser` для налаштування обробки cookie в додатку. `cookieParser` дозволяє отримувати доступ до токену, що зберігається в cookie. Без `cookieParser` ви не зможете отримати доступ до cookie з заголовків запиту. Введіть в термінал:
npm i cookie-parser
Тепер, коли `cookieParser` встановлений, налаштуйте додаток, оновивши файл `index.js` та додавши проміжне ПЗ до маршруту `’/user/’`.
файл index.js
const cookieParser = require('cookie-parser'); const express = require('express'); const dotenv = require('dotenv'); //Конфігурація dotenv перед іншими файлами dotenv.config({path:'./config/config.env'}); require('./config/conn'); //Створення екземпляра express const app = express(); const route = require('./routes/userRoute'); //Для обробки JSON даних app.use(express.json()); //Налаштування cookie-parser app.use(cookieParser()); //Використання маршрутів app.use('/api', route); //Запуск сервера app.listen(process.env.PORT,()=> { console.log(`Сервер запущено на порті ${process.env.PORT}`); })
userRoute.js
//Імпорт необхідних файлів та бібліотек const express = require('express'); const bcrypt = require('bcryptjs'); const jwt = require('jsonwebtoken'); const isAuthenticated = require('../middleware/auth'); //Створення роутера express const route = express.Router(); //Імпорт userModel const userModel = require('../models/userModel'); //Створення маршруту для отримання даних користувача route.get('/user', isAuthenticated, async (req, res) => { try { const user = await userModel.find(); if (!user) { return res.json({ message: 'Користувача не знайдено' }) } return res.json({ user: user }) } catch (error) { return res.json({ error: error }); } }) module.exports = route;
Маршрут `’/user’` доступний тільки для авторизованих користувачів.
Перевірка API в Postman
Перед тим як перевірити API, потрібно оновити файл `package.json`, додавши наступний код:
"scripts": { "test": "echo "Error: no test specified" && exit 1", "start": "node index.js", "dev": "nodemon index.js" },
Ви можете запустити сервер командою `npm start`, але це запустить його лише один раз. Для того, щоб сервер автоматично перезапускався при зміні коду, вам знадобиться `nodemon`. Встановіть його, виконавши в терміналі:
npm install -g nodemon
Прапорець `-g` дозволить встановити `nodemon` глобально. Вам не потрібно буде встановлювати його знову для кожного нового проекту.
Для запуску сервера використовуйте команду `npm run dev` в терміналі. Ви повинні побачити наступний результат:
Тепер, коли код готовий, та сервер запущений, перейдіть до Postman для перевірки роботи API.
Що таке Postman?
Postman – це інструмент для розробки та тестування API.
Якщо Postman не встановлено, завантажте його з офіційного сайту Postman.
Відкрийте Postman, створіть нову колекцію з назвою `nodeAPItest` та додайте три запити: `register`, `login` та `user`. Ви повинні отримати наступну структуру:
При відправці JSON даних на `localhost:5000/api/register` ви повинні отримати схожу відповідь:
Оскільки при реєстрації ми також створюємо токени та зберігаємо їх в cookie, ви можете отримати інформацію про користувача, запитуючи маршрут `localhost:5000/api/user`. Інші запити ви можете перевірити в Postman самостійно.
Повний код можна знайти в моєму github-репозиторії.
Висновок
У цьому посібнику ми навчились інтегрувати автентифікацію в NodeJS API за допомогою JWT токенів. Також надали авторизованим користувачам доступ до даних профілю.
Бажаємо вам успіхів у програмуванні!