Аутентификация против авторизации: эндпоинт, который может вызвать каждый
Эти два слова звучат похоже, а разница между ними — это место, где живут настоящие взломы.
- Аутентификация (auth) — это кто ты? — вход в систему, доказательство личности.
- Авторизация — это что тебе разрешено делать? — может ли этот залогиненный пользователь выполнить это действие с этими данными.
С аутентификацией AI справляется сносно; большую часть берут на себя библиотеки. На авторизации он стабильно проваливается, потому что авторизация специфична для правил вашего приложения, а их AI не знает. Хрестоматийная катастрофа:
// VULNERABLE: проверяет, что ты залогинен, но не КТО ты
app.get("/admin/export-all-users", requireLogin, (req, res) => {
res.json(db.getAllUsers()); // любой залогиненный пользователь может это дёрнуть
});
Этот эндпоинт «защищён» — нужно быть залогиненным. Но любой залогиненный пользователь, включая того, кто зарегистрировался тридцать секунд назад, может вызвать его и скачать данные всех пользователей. Аутентификация есть, авторизации нет. Исправление — проверять права на самом действии:
// SAFE: подтверждает, что этот пользователь действительно админ
app.get("/admin/export-all-users", requireLogin, (req, res) => {
if (!req.user.isAdmin) return res.status(403).send("Forbidden");
res.json(db.getAllUsers());
});
Представьте две двери, через которые должен пройти запрос. Аутентификация спрашивает «вы вошли?»; авторизация спрашивает «можно ли вам делать это?». Брешь возникает, когда приложение строит первую дверь и забывает вторую:
запрос ──▶ ┌──────────────────┐ ──▶ ┌──────────────────┐ ──▶ действ
│ АУТЕНТИФИКАЦИЯ │ │ АВТОРИЗАЦИЯ │ вып.
│ "вошёл?" │ │ "можно ли ЭТОМУ │
│ │ │ это ДЕЙСТВИЕ?" │
└──────────────────┘ └──────────────────┘
✓ почти все ✗ часто нет
делают это → вот взлом
ПРИМЕР — /admin/export-all-users
вошедший юзер ──▶ [auth ✓] ──▶ [ нет authz-проверки] ──▶ слиты ВСЕ юзеры
вошедший юзер ──▶ [auth ✓] ──▶ [ isAdmin? → 403 ] ──▶ блокир. ✓
Та же ловушка в миниатюре встречается повсюду: эндпоинт, который возвращает заказ #1234, не проверяя, что заказ принадлежит запрашивающему пользователю. Любой может поменять номер в и прочитать чужой заказ. Правило непарадное и абсолютное: проверяйте авторизацию на каждом эндпоинте, который касается данных, и не доверяйте ID, пришедшему от клиента. Не считайте скрытый URL безопасным только потому, что он скрыт — «никто не знает, что это существует» — это не средство защиты.