Реализация ролевого контроля доступа (RBAC) с Passport.js и JWT

Определение: Ролевой контроль доступа (RBAC) — модель управления доступом, в которой разрешения ассоциированы с ролями, а пользователи получают роли.
Важно: RBAC — это про авторизацию (кто что может делать), а не про аутентификацию (кто вы есть).
Зачем использовать RBAC
- Централизованный контроль прав. Администратор назначает роли — система применяет права.
- Простота управления в больших командах и системах.
- Чёткое разделение обязанностей для соответствия требованиям безопасности.
Примеры ролей: admin, editor, user, guest.
Подходы к реализации RBAC
Основные варианты:
- Использовать специализированные библиотеки RBAC (например, готовые ACL/RBAC модули). Это удобно при сложных правилах и иерархиях.
- Интегрировать RBAC в существующую систему аутентификации (Passport.js + JWT). Хорошо подходит для простых и средних по сложности приложений.
В этой статье показан второй вариант: хранение роли в JWT и проверка роли в middleware.
ALT изображения: Открытый ноутбук с редактором кода на экране
Архитектурная идея в одном предложении
При логине пользователю присваивается JWT с полем role. Для защищённых маршрутов middleware проверяет токен и сравнивает роль с требуемой.
Шаг 1 — Установка и подготовка проекта
- Инициализируйте проект Node.js и установите зависимости:
npm install express cors dotenv mongoose cookie-parser jsonwebtoken mongodb passport passport-local- Создайте файл
.envв корне проекта и добавьте строки:
CONNECTION_URI="connection URI"
SECRET_KEY="This is a sample secret key."Замените CONNECTION_URI и SECRET_KEY на реальные значения перед деплоем.
Шаг 2 — Подключение к MongoDB
Создайте файл utils/db.js и добавьте код для подключения через Mongoose:
const mongoose = require('mongoose');
const connectDB = async () => {
try {
await mongoose.connect(process.env.CONNECTION_URI);
console.log("Connected to MongoDB!");
} catch (error) {
console.error("Error connecting to MongoDB:", error);
}
};
module.exports = connectDB;Шаг 3 — Модель пользователя
Создайте models/user.model.js с простой схемой:
const mongoose = require('mongoose');
const userSchema = new mongoose.Schema({
username: String,
password: String,
role: String
});
module.exports = mongoose.model('User', userSchema);Примечание: Для продакшн-решений пароли нужно хранить в виде хэшей (bcrypt) и добавлять валидацию.
Шаг 4 — Контроллеры пользователей
Создайте controllers/user.controller.js и добавьте логику регистрации, логина и получения списка пользователей:
const User = require('../models/user.model');
const passport = require('passport');
const { generateToken } = require('../middleware/auth');
require('../middleware/passport')(passport);
exports.registerUser = async (req, res) => {
const { username, password, role } = req.body;
try {
await User.create({ username, password, role });
res.status(201).json({ message: 'User registered successfully' });
} catch (error) {
console.log(error);
res.status(500).json({ message: 'An error occurred!' });
}
};
exports.loginUser = (req, res, next) => {
passport.authenticate('local', { session: false }, (err, user, info) => {
if (err) {
console.log(err);
return res.status(500).json({
message: 'An error occurred while logging in'
});
}
if (!user) {
return res.status(401).json({
message: 'Invalid login credentials'
});
}
req.login(user, { session: false }, (err) => {
if (err) {
console.log(err);
return res.status(500).json({
message: 'An error occurred while logging in'
});
}
const { _id, username, role } = user;
const payload = { userId: _id, username, role };
const token = generateToken(payload);
res.cookie('token', token, { httpOnly: true });
return res.status(200).json({ message: 'Login successful' });
});
})(req, res, next);
};
exports.getUsers = async (req, res) => {
try {
const users = await User.find({});
res.json(users);
} catch (error) {
console.log(error);
res.status(500).json({ message: 'An error occurred!' });
}
};Пояснения:
- registerUser: создаёт запись пользователя с полем role.
- loginUser: аутентифицирует через Passport, генерирует JWT с ролью и кладёт токен в cookie.
- getUsers: защищённый маршрут для демонстрации проверки роли.
Шаг 5 — Passport.js локальная стратегия
Создайте middleware/passport.js:
const LocalStrategy = require('passport-local').Strategy;
const User = require('../models/user.model');
module.exports = (passport) => {
passport.use(
new LocalStrategy(async (username, password, done) => {
try {
const user = await User.findOne({ username });
if (!user) {
return done(null, false);
}
if (user.password !== password) {
return done(null, false);
}
return done(null, user);
} catch (error) {
return done(error);
}
})
);
};Важно: В реальном приложении сравнивайте хэши паролей (bcrypt) вместо прямого сравнения.
Шаг 6 — Middleware для JWT: генерация и верификация
Создайте middleware/auth.js:
const jwt = require('jsonwebtoken');
const secretKey = process.env.SECRET_KEY;
const generateToken = (payload) => {
const token = jwt.sign(payload, secretKey, { expiresIn: '1h' });
return token;
};
const verifyToken = (requiredRole) => (req, res, next) => {
const token = req.cookies.token;
if (!token) {
return res.status(401).json({ message: 'No token provided' });
}
jwt.verify(token, secretKey, (err, decoded) => {
if (err) {
return res.status(401).json({ message: 'Invalid token' });
}
req.userId = decoded.userId;
if (decoded.role !== requiredRole) {
return res.status(403).json({
message: 'You do not have the authorization and permissions to access this resource.'
});
}
next();
});
};
module.exports = { generateToken, verifyToken };Как это работает: middleware читает cookie token, верифицирует подпись и проверяет поле role.
Шаг 7 — Маршруты API
Создайте routes/userRoutes.js:
const express = require('express');
const router = express.Router();
const userControllers = require('../controllers/user.controller');
const { verifyToken } = require('../middleware/auth');
router.post('/api/register', userControllers.registerUser);
router.post('/api/login', userControllers.loginUser);
router.get('/api/users', verifyToken('admin'), userControllers.getUsers);
module.exports = router;Пояснение: маршрут /api/users доступен только пользователям с ролью admin.
Шаг 8 — Основной файл сервера
Обновите server.js:
const express = require('express');
const cors = require('cors');
const cookieParser = require('cookie-parser');
const app = express();
const port = 5000;
require('dotenv').config();
const connectDB = require('./utils/db');
const passport = require('passport');
require('./middleware/passport')(passport);
connectDB();
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(cors());
app.use(cookieParser());
app.use(passport.initialize());
const userRoutes = require('./routes/userRoutes');
app.use('/', userRoutes);
app.listen(port, () => {
console.log(`Server is running on port ${port}`);
});Запустите сервер:
node server.jsТестирование и критерии приёмки
Краткий набор тестов для проверки RBAC:
- Регистрация: POST /api/register — возвращает 201 и создаёт пользователя с ролью.
- Логин: POST /api/login — выдаёт cookie
tokenи код 200 при корректных данных. - Доступ к /api/users:
- с ролью admin — возвращает список пользователей (200);
- с ролью user или без токена — возвращает 403 или 401.
Критерии приёмки:
- Пароли хранятся как хэши (в реальном проекте).
- Токен имеет срок жизни (например, 1 час).
- Роль пользователя присутствует в полезной нагрузке (payload) токена.
Рекомендации по безопасности и харднинг
- Хэшируйте пароли с bcrypt (или Argon2).
- Используйте короткий срок жизни access-токенов и refresh-токены с безопасным хранением.
- Подписывайте JWT надёжным SECRET_KEY и используйте окружение для секретов.
- Защитите cookie: httpOnly, secure (в HTTPS), sameSite по необходимости.
- Логи: не записывайте секреты, не логируйте токены.
- Валидация входных данных: строго валидируйте тело запроса и параметры.
- Применяйте rate limiting на маршруты логина.
Важно: RBAC с JWT использует утверждения (claims) внутри токена. Если роль меняется в базе, токен с прежней ролью остаётся действительным до истечения — учитывайте это при проектировании.
Модели зрелости RBAC (простая шкала)
- Уровень 0 — Отсутствие контроля (все доступы открыты).
- Уровень 1 — Жёсткие роли (admin/user) в приложении, проверка на уровне маршрутов.
- Уровень 2 — Разделение прав и группировка ролей; хранение ролей в БД; централизованные политики.
- Уровень 3 — Динамические правила, временные права, аудит и управление правами через UI.
Когда RBAC не подходит / альтернативы
- Сильно динамичные права на ресурсах (например, разрешения зависят от атрибутов объекта) — рассмотрите ABAC (attribute-based access control).
- Сложные иерархии/наследование правил — используйте специализированные RBAC/ACL библиотеки.
Пошаговый чеклист для внедрения RBAC (роль: разработчик)
- Добавить поле role в модель пользователя.
- Добавить хранение роли при регистрации.
- Генерировать JWT с полем role при логине.
- Создать middleware verifyToken(requiredRole).
- Защитить маршруты, указав требуемую роль.
- Реализовать хеширование паролей.
- Настроить безопасные cookie.
- Добавить тесты доступа и интеграционные тесты.
Шаблон сценариев тестирования (минимум)
- Регистрация нового admin-пользователя и проверка роли.
- Регистрация пользователя без роли — проверить поведение (валидация).
- Попытка доступа к /api/users без токена — должен быть 401.
- Попытка доступа с токеном role=user — должен быть 403.
- Попытка доступа с токеном role=admin — должен быть 200.
Мини-методология внедрения в проект
- Подготовьте миграцию модели (добавьте role).
- Внедрите хеширование паролей.
- Добавьте генерацию JWT с role.
- Внедрите middleware и защитите ключевые маршруты.
- Напишите тесты и проведите ревью безопасности.
- Разверните на staging и выполните контроль доступа с разными ролями.
Советы по миграции (legacy → RBAC)
- Сначала внедрите чтение роли, не выбрасывая старые проверки.
- Обновите админ-панель для назначения ролей пользователям.
- Запустите миграцию: по умолчанию назначьте роль
userсуществующим пользователям. - Обновите документацию API.
Decision flow: проверка доступа (Mermaid)
flowchart TD
A[Запрос к защищённому маршруту] --> B{Есть cookie token?}
B -- Нет --> C[Ответ 401: No token provided]
B -- Да --> D[Верификация подписи JWT]
D --> E{Токен валиден?}
E -- Нет --> F[Ответ 401: Invalid token]
E -- Да --> G[Проверка поля role в токене]
G --> H{role === requiredRole?}
H -- Нет --> I[Ответ 403: Нет прав]
H -- Да --> J[next'' — Доступ открыт]Примеры альтернативных реализаций
- Хранить права отдельно от ролей и проверять соответствие правил на уровне ресурсов.
- Использовать готовые RBAC/ACL библиотеки для управления сложными правами (например, Casbin для гибкой политики).
Частые ошибки и как их избежать
- Хранение паролей в открытом виде — всегда хэшируйте.
- Полагаться только на роль в токене при долгом сроке жизни — используйте короткий срок действия и механизм отзыва токенов.
- Не проверять входные данные — это приводит к уязвимостям.
Локальные особенности для развёртывания (RU/EU)
- Храните секреты в управляемом хранилище (Vault, AWS Secrets Manager) и не в git.
- При обработке персональных данных соблюдайте локальное законодательство (например, GDPR в ЕС).
Факто-бокс: ключевые точки
- JWT: удобен для распределённых систем, но токены трудно отзыввать.
- Роль в payload: быстро и просто, но нужно учитывать обновление ролей.
- Безопасность: httpOnly + secure cookies, хэширование паролей, короткий срок действия токенов.
Роль-based чеклист для администратора
- Проверить, что у новых пользователей назначена корректная роль.
- Отозвать доступ у уволенных сотрудников и изменить их роли.
- Проверить логи доступа для подозрительных входов.
Короткий глоссарий
- JWT — JSON Web Token: компактный токен для передачи утверждений.
- RBAC — Role-Based Access Control: модель управления доступом по ролям.
- Passport.js — middleware для аутентификации в Node.js.
Часто задаваемые вопросы
Можно ли хранить роль в базе и не класть её в JWT?
Да. Тогда при каждом запросе требуются дополнительные обращения к базе для проверки роли. Это безопаснее в плане отзывов прав, но дороже по производительности.
Как отозвать JWT до истечения срока?
- Используйте короткие сроки жизни access-токенов и механизм refresh-токенов.
- Храните чёрный список отозванных токенов или храните версию токена в базе (tokenVersion) и сравнивайте.
Куда смотреть дальше
- Добавьте поддержку ролей с иерархией и правами на уровне ресурсов.
- Реализуйте аудит эффективности (кто что менял и когда).
Итог: RBAC с Passport.js и JWT — практичное решение для многих приложений. Оно простое в реализации и даёт ясную модель управления доступом. При необходимости масштабируйте модель до ABAC или подключайте специализированные движки политик.
Похожие материалы
Как найти приложения для Android Wear 2.0
Как размыть фон в Canva: 3 простых способа
Установить и протестировать Windows 10 S
RRoD на Xbox 360: как устранить и диагностировать
Как проверить версию Ubuntu