Аутентификация против авторизации: эндпоинт, который может вызвать каждый
Эти два слова звучат похоже, а разница между ними — это место, где живут настоящие взломы.
- Аутентификация (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());
});
Та же ловушка в миниатюре встречается повсюду: эндпоинт, который возвращает заказ #1234, не проверяя, что заказ принадлежит запрашивающему пользователю. Любой может поменять номер в URL и прочитать чужой заказ. Правило непарадное и абсолютное: проверяйте авторизацию на каждом эндпоинте, который касается данных, и не доверяйте ID, пришедшему от клиента. Не считайте скрытый URL безопасным только потому, что он скрыт — «никто не знает, что это существует» — это не средство защиты.