Впровадження RBAC (Role-Based Access Control) для веб-додатків
Користувач входить у систему і бачить рівно те, що він авторизований бачити — не більше і не менше. Звучить тривіально, поки ви не починаєте рахувати: 12 типів користувачів, 40 розділів інтерфейсу, матриця дозволів на аркуші A3, яку потрібно підтримувати в коді. RBAC — стандартна відповідь: права призначаються не користувачам напрямо, а ролям, а користувачі отримують ролі.
Модель даних
Мінімальна схема для PostgreSQL:
CREATE TABLE roles (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL UNIQUE, -- 'admin', 'editor', 'viewer'
description TEXT
);
CREATE TABLE permissions (
id SERIAL PRIMARY KEY,
resource VARCHAR(128) NOT NULL, -- 'articles', 'users', 'reports'
action VARCHAR(64) NOT NULL, -- 'create', 'read', 'update', 'delete', 'publish'
UNIQUE (resource, action)
);
CREATE TABLE role_permissions (
role_id INT REFERENCES roles(id) ON DELETE CASCADE,
permission_id INT REFERENCES permissions(id) ON DELETE CASCADE,
PRIMARY KEY (role_id, permission_id)
);
CREATE TABLE user_roles (
user_id INT REFERENCES users(id) ON DELETE CASCADE,
role_id INT REFERENCES roles(id) ON DELETE CASCADE,
PRIMARY KEY (user_id, role_id)
);
Це класична реалізація RBAC0. Для ієрархічних ролей (admin наслідує всі права editor), додайте таблицю role_hierarchy з полями parent_role_id / child_role_id та використовуйте рекурсивний CTE при перевірці.
Перевірка дозволів на бекенді
Node.js + Express, middleware-підхід:
// permissions.js — завантажуємо дозволи з бази при старті або кешуємо в Redis
async function loadUserPermissions(userId) {
const rows = await db.query(`
SELECT DISTINCT p.resource, p.action
FROM user_roles ur
JOIN role_permissions rp ON rp.role_id = ur.role_id
JOIN permissions p ON p.id = rp.permission_id
WHERE ur.user_id = $1
`, [userId]);
return new Set(rows.map(r => `${r.resource}:${r.action}`));
}
// middleware/can.js
function can(resource, action) {
return async (req, res, next) => {
const perms = await loadUserPermissions(req.user.id);
if (perms.has(`${resource}:${action}`)) {
return next();
}
res.status(403).json({ error: 'Forbidden' });
};
}
// routes
router.delete('/articles/:id', authenticate, can('articles', 'delete'), deleteArticle);
router.post('/articles', authenticate, can('articles', 'create'), createArticle);
Для PHP/Laravel паттерн аналогічний — Gate та Policy:
// AuthServiceProvider
Gate::before(function (User $user, string $ability) {
if ($user->hasRole('superadmin')) {
return true; // superadmin обходить всі перевірки
}
});
Gate::define('articles.delete', function (User $user) {
return $user->hasPermission('articles', 'delete');
});
// У контролері
public function destroy(Article $article)
{
$this->authorize('articles.delete');
$article->delete();
return response()->noContent();
}
Метод hasPermission робить один запит із JOIN або бере з кешу — залежить від навантаження.
Кеширування матриці дозволів
Гоняти JOIN через три таблиці на кожен HTTP-запит — витратно. Дозволи користувача змінюються рідко — це добре кешується:
// redis cache, TTL 5 хвилин
async function getUserPermissions(userId) {
const cacheKey = `user_perms:${userId}`;
const cached = await redis.get(cacheKey);
if (cached) return new Set(JSON.parse(cached));
const perms = await loadUserPermissions(userId);
await redis.setex(cacheKey, 300, JSON.stringify([...perms]));
return perms;
}
// Інвалідація при зміні ролей користувача
async function assignRole(userId, roleId) {
await db.query(
'INSERT INTO user_roles (user_id, role_id) VALUES ($1, $2) ON CONFLICT DO NOTHING',
[userId, roleId]
);
await redis.del(`user_perms:${userId}`);
}
Управління ролями в інтерфейсі
Адміністратор повинен бачити й редагувати матрицю. Мінімальний API:
GET /api/roles — список ролей
POST /api/roles — створити роль
GET /api/roles/:id/permissions — дозволи ролі
PUT /api/roles/:id/permissions — оновити дозволи ролі (масив permission_ids)
GET /api/users/:id/roles — ролі користувача
POST /api/users/:id/roles — призначити роль
DELETE /api/users/:id/roles/:roleId — зняти роль
На фронті таблиця з чекбоксами resource × action — стандартний UI для такого екрану.
Що впливає на терміни
Базова реалізація (3–5 ролей, без наслідування, без UI управління ролями) — 2–3 дні. Це схема, middleware, інтеграція з існуючою аутентифікацією, тести.
Якщо додається UI для управління ролями та дозволами — ще 2 дні. Якщо ієрархічні ролі або мультитенантність (ролі ізольовані по організації) — додаємо ще 2–3 дні на ускладнення схеми та логіки перевірок.







