Почему нельзя полагаться только на URL для разграничения ролей
При построении масштабируемого API часто возникает вопрос: достаточно ли разделить маршруты по префиксам, например /api/admin/categories, и тем самым ограничить доступ? На первый взгляд такой подход кажется простым, но он вносит несколько скрытых проблем:
- Тесная связь маршрута и роли – URL становится частью бизнес‑логики. При изменении ролей или добавлении новых прав потребуется менять сам путь, а не только проверку доступа.
- Трудности масштабирования – каждый новый тип пользователя (редактор, модератор и т.п.) подразумевает создание отдельного набора URL, что быстро приводит к «разрастанию» маршрутов.
- Нарушение принципов REST – путь
/api/admin/...явно указывает на роль, а не на ресурс, что противоречит идее, что URL описывает только сущность, а не контекст доступа. - Утечка логики авторизации в роутинг – проверка прав оказывается разбросанной по файлам маршрутов, усложняя поддержку и тестирование.
Эти причины делают URL‑ориентированное разграничение ролей плохой практикой для production‑приложений.
Middleware‑ориентированная авторизация как стандарт
Оптимальный способ – оставить URL чистыми и REST‑соответствующими, а проверку прав вынести в промежуточный слой (middleware). В таком варианте маршруты описывают только операции над ресурсом, а доступ контролируется до выполнения контроллера.
// GET /api/categories → доступен всем
// POST /api/categories → только администраторы
// PUT /api/categories/:id → только администраторы
// DELETE /api/categories/:id → только администраторы
Для реализации используется два уровня middleware:
authMiddleware– проверяет наличие и валидность токена, а также извлекает идентификатор пользователя.adminMiddleware– проверяет, имеет ли аутентифицированный пользователь роль администратора (или любую другую роль, заданную в конфигурации).
Такой подход гарантирует, что любые изменения в политике доступа требуют правки лишь в одном месте, а не в каждом маршруте.
Архитектура проекта с модульным разделением
Для поддерживаемости стоит придерживаться feature‑based структуры, при которой каждый модуль инкапсулирует свои контроллеры, сервисы, модели и роуты. Пример дерева проекта:
src/
├── modules/
│ ├── categories/
│ │ ├── categories.controller.ts
│ │ ├── categories.service.ts
│ │ ├── categories.routes.ts
│ │ └── categories.model.ts
│ └── users/
│ └── ... (аналогично)
├── middleware/
│ ├── auth.middleware.ts
│ └── admin.middleware.ts
└── app.ts
- Контроллер отвечает только за бизнес‑логику и формирование ответа.
- Сервис содержит операции над данными (работа с БД, кэш и т.п.).
- Routes связывают HTTP‑методы с контроллерами и подключают нужные middleware.
- Middleware реализует отдельные аспекты (аутентификация, авторизация, логирование).
Пример реализации роутов для категорий
// src/modules/categories/categories.routes.ts
import { Router } from 'express';
import { CategoriesController } from './categories.controller';
import { authMiddleware } from '../../middleware/auth.middleware';
import { adminMiddleware } from '../../middleware/admin.middleware';
const router = Router();
/* Публичные операции */
router.get('/', CategoriesController.getAll);
/* Операции, требующие прав администратора */
router.post(
'/',
authMiddleware, // проверка токена
adminMiddleware, // проверка роли
CategoriesController.create
);
router.put(
'/:id',
authMiddleware,
adminMiddleware,
CategoriesController.update
);
router.delete(
'/:id',
authMiddleware,
adminMiddleware,
CategoriesController.remove
);
export default router;
В этом фрагменте видно, как middleware ставятся между определением пути и вызовом контроллера. При отсутствии токена или нужной роли запрос будет прерван до попадания в бизнес‑логику.
Преимущества middleware‑подхода
| Показатель | Почему middleware выигрывает |
|---|---|
| Чистота API | URL описывает только ресурс, а не роль пользователя. |
| Масштабируемость | Добавление новых ролей (editor, moderator) требует лишь новых middleware‑файлов и их подключения. |
| Разделение ответственности | Маршруты отвечают за «что», middleware – за «кто», контроллер – за «как». |
| Тестируемость | Каждый слой можно покрыть отдельными юнит‑тестами, не смешивая их. |
| Безопасность | Централизованная проверка прав снижает риск пропустить ограничение в отдельном маршруте. |
Финальная схема API
| Метод | Путь | Доступ |
|---|---|---|
| GET | /api/categories | Публичный |
| POST | /api/categories | Только администратор |
| PUT | /api/categories/:id | Только администратор |
| DELETE | /api/categories/:id | Только администратор |
Эта модель легко расширяется: достаточно добавить новые middleware‑компоненты и привязать их к нужным маршрутам. При таком построении backend остаётся гибким, поддерживаемым и готовым к росту функциональности без необходимости переписывать URL‑структуру.