Головна / Статті

Контроль доступу за ролями в Express з JWT та двома мідлвейрами

Дізнайтеся, як забезпечити доступ на основі ролей у API Express, поєднавши проміжок автентифікації JWT із багаторазово використовуваним захисником authorize(), а також коли слід повертати коди 401 та 403.

Видавець: REACTAPP.TOP4 хв читання1024 слів

Джерела: What is RBAC in Node.js?

У більшості бекендів різним користувачам дозволяється виконувати різні дії: адміністратор може видаляти облікові записи, менеджер — редагувати їх, а звичайний користувач може лише читати. Розміщення перевірок у форматі if (user.role === ...) у контролерах швидко робить код некерованим та схильним до помилок. Цей посібник показує, як сформулювати ці правила за допомогою контролю доступу на основі ролей (RBAC) у Express за допомогою двох невеликих мідлверсів, а також як вибрати правильний код статусу при відхиленні запиту.

Типова матриця дозволів виглядає так:

Admin   → Create, Read, Update, Delete
Manager → Read, Update
User    → Read

Як RBAC вписується у процес обробки запитів

RBAC — це модель авторизації: доступ до кінцевої точки надається або відмовляється залежно від ролі, присвоєної користувачеві. У API Node.js на основі JWT елементи виконуються у фіксованому порядку:

Login
  ↓
JWT Token
  ↓
Authentication Middleware
  ↓
Check User Role
  ↓
Authorization Middleware
  ↓
Controller

У процесі відповідається на два окремі запитання. Аутентифікація визначає, хто є користувачем. Ауторизація вирішує, що цей користувач може робити. Розділення їх у окремих мідлвейрах робить кожен з них простим та піддаємим тестуванню. Це розрізнення детальніше розглядається у статті про те, де мають бути аутентифікація та ауторизація у вашому коді.

Призначення ролей ендпоїнтам

Розгляньмо стандартний набір маршрутів для керування користувачами:

GET    /api/users
POST   /api/users
PUT    /api/users/:id
DELETE /api/users/:id

Бажана політика для них:

Admin   → All APIs
Manager → GET, PUT
User    → GET only

Замість повторної перевірки ролі в кожному контролері, політику можна декларативно приєднати до кожного маршруту.

Додавання ролі до токена

Кожен запис користувача містить роль:

{
  name: "Ash",
  email: "ash@example.com",
  role: "admin"
}

Під час входу роль записується у JWT разом із ідентифікатором користувача, тож подальші запити можна авторизувати без пошуку в базі даних:

const token = jwt.sign(
  {
    userId: user._id,
    role: user.role
  },
  process.env.JWT_SECRET,
  { expiresIn: "1h" }
);

Компроміс: роль залишається незмінною у токені до його закінчення терміну дії. Якщо адміністратора понижують у рангу, старий токен все ще вказуватиме admin протягом години. Короткий термін дії обмежує цей проміжок часу; для конфіденційних операцій безпечніше знову читати інформацію про роль з бази даних.

Аутентифікація запиту

Перший мідлвейр отримує токен з заголовка Authorization: Bearer <token>, перевіряє його підпис та термін дії, а потім зберігає розшифрований вміст у полі req.user. Відсутні чи недійсні токени призводять до завершення запиту з кодом 401:

const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(" ")[1];
if (!token) {
    return res.status(401).json({
      message: "Authentication required"
    });
  }
  try {
    const decoded = jwt.verify(
      token,
      process.env.JWT_SECRET
    );
    req.user = decoded;
    next();
  } catch (error) {
    return res.status(401).json({
      message: "Invalid or expired token"
    });
  }
};

Після його виконання кожен наступний обробник може довіряти req.user. Під час перевірки також є гарною практикою вказати допустимі алгоритми через параметр algorithms у функції jwt.verify, замість того щоб приймати все, що вказано у заголовку токена.

Авторизація за роллю

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

const authorize = (...roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({
        message: "Access denied"
      });
    }
next();
  };
};

Зауважте, що він читає req.user.role, тому його завжди потрібно виконувати після authenticate; інакше він винесе помилку через відсутність значення req.user.

Захист маршруту тепер полягає у поєднанні цих двох елементів:

router.delete(
  "/users/:id",
  authenticate,
  authorize("admin"),
  deleteUser
);

До функції deleteUser може отримати доступ лише автентифікований адміністратор. Дозволення кількох ролей означає лише необхідність передачі більшої кількості аргументів:

router.put(
  "/users/:id",
  authenticate,
  authorize("admin", "manager"),
  updateUser
);

Тут як адміністратори, так і менеджери можуть оновлювати користувачів.

Вибір між 401 та 403

Ці два коди часто плутають, але вони вказують на різні проблеми.

401: звернулийся не автентифікований

Сервер не знає, хто здійснює запит, зазвичай через одну з наступних причин:

No token
Invalid token
Expired token

Клієнт повинен знову увійти або оновити свій токен.

403: звернулийся відомий, але не має дозволу

Ідентичність підтверджена, проте роль не надає доступу:

User role = user
Required role = admin

Повторна спроба з тими самими обліковими даними не допоможе. Коротке правило: 401 означає «ми не знаємо, хто ви»; 403 означає «ми знаємо, і відповідь — ні».

Швидкий огляд

  • Що таке RBAC? Контроль доступу, де права API визначаються на основі ролі, призначеної звернуломуся.
  • Як це реалізовано в Express? Один мідлвейр перевіряє JWT, а другий перевіряє роль у порівнянні зі списком дозволених маршрутів.
  • Де необхідно це застосовувати? На бекенді. Приховування кнопок у інтерфейсі є лише декоративним, оскільки будь-хто може безпосередньо викликати API.
  • Чому саме мідлвейри? Вони забезпечують можливість повторного використання механізмів авторизації та виводять їх за межі бізнес-логіки, тож один захисний елемент може охороняти багато маршрутів.
  • Що не охоплює RBAC? Правила власності, такі як «користувачі можуть редагувати лише власний профіль», вимагають додаткової перевірки самого ресурсу.
  • Підсумок

    Повний процес є простим:

    Client
      ↓
    JWT
      ↓
    Authentication
      ↓
    Authorization / RBAC
      ↓
    Controller
      ↓
    Database
    

    Аутентифікуйте того, хто звертається, прочитайте його роль, порівняйте її з тим, що дозволяє маршрут, а потім дозвольте доступ або відхиліть його. У міру зростання додатку розгляньте можливість прив’язки ролей до більш детальних дозволів, таких як user:create, user:update та user:delete, та перевірки дозволів у маршрутах замість назв ролей. Тоді додавання нової ролі означатиме лише зміну одного мапування, а не кожного маршруту.