Безопасность: helmet, CORS, rate-limit
helmet, cors и rate-limit — три кита базовой безопасности любого Express-API.
«Эти три middleware решают разные задачи и работают вместе — ставь все три по умолчанию.»
Открытое в интернет приложение мгновенно становится мишенью: боты сканируют порты, перебирают пароли, ищут дыры. Минимальная защита Express строится на трёх middleware: helmet ставит безопасные заголовки, cors контролирует кросс-доменный доступ, rate-limit ограничивает частоту запросов. В этом уроке разберём каждый.
helmet: защитные заголовки
helmet — это набор маленьких middleware, которые выставляют HTTP-заголовки, прикрывающие типовые уязвимости (clickjacking, угадывание типа контента и т.д.):
const helmet = require('helmet');
app.use(helmet()); // одна строка -- десяток защитных заголовков
// X-Content-Type-Options, X-Frame-Options,
// Strict-Transport-Security и другиеПо словам авторов, helmet — самое высокоэффективное одиночное улучшение безопасности Express: одна строка закрывает целый класс проблем.
cors: кто может звать твой API
Браузер по умолчанию запрещает странице с одного домена звать API на другом. CORS-заголовки явно разрешают доверенные источники:
const cors = require('cors');
// разрешаем только свой фронтенд, а не всех подряд
app.use(cors({ origin: 'https://myapp.ru' }));Открывать CORS всем (origin: '*') для API с авторизацией опасно — указывай конкретные домены.
rate-limit: защита от перебора
const rateLimit = require('express-rate-limit');
const limiter = rateLimit({
windowMs: 15 * 60 * 1000, // окно 15 минут
max: 100 // не больше 100 запросов с одного IP
});
app.use(limiter);Порядок подключения
app.use(helmet()) -> 1. базовая защита заголовками app.use(cors(...)) -> 2. правила кросс-доменного доступа app.use(limiter) -> 3. ограничение частоты app.use(express.json())-> 4. парсинг тела маршруты -> 5. логика обработчик ошибок -> последним
Скользящее окно лимита в браузере
Смоделируем rate limiter: считаем запросы в окне времени и блокируем превышение. Это упрощённая версия того, что делает express-rate-limit:
function createLimiter(maxRequests, windowMs) {
let hits = [];
return function allow(now) {
hits = hits.filter(t => now - t < windowMs); // выкидываем старые
if (hits.length >= maxRequests) return false; // лимит исчерпан
hits.push(now);
return true;
};
}
const allow = createLimiter(3, 1000); // 3 запроса в секунду
console.log(allow(0)); // true
console.log(allow(100)); // true
console.log(allow(200)); // true
console.log(allow(300)); // false -- превышен лимит
console.log(allow(1300));// true -- окно сдвинулосьКак работает под капотом
helmet просто добавляет middleware, которые на каждый ответ дописывают заголовки безопасности — браузер их читает и применяет защиту. cors отвечает на предварительный запрос браузера (preflight OPTIONS) и проставляет заголовки Access-Control-Allow-*. rate-limit держит счётчик запросов по IP в окне времени и при превышении возвращает 429 Too Many Requests, не пуская запрос дальше по цепочке.
Частые ошибки
- origin: '*' с куками/токенами. Это открывает API всему интернету. Указывай конкретные домены.
- Лимитер после маршрутов. Защита должна стоять до логики, иначе она бесполезна.
- Считать helmet достаточным. Это базовый минимум, а не полная защита; нужны ещё валидация и аутентификация.
Best practices
- Подключай helmet, cors и rate-limit как стандартный базовый набор любого API.
- Ставь их в начале цепочки, до парсеров и маршрутов.
- Особо строгий лимит — на чувствительные маршруты вроде /login.
Итоги
helmet, cors и rate-limit — три разных, дополняющих друг друга средства защиты. Они закрывают базу, но не отменяют валидацию данных и аутентификацию. Дальше разберём, как хранить секреты приложения и как переключаться между окружениями разработки и продакшена.
Глубина важнее одной строки
helmet, cors и rate-limit закрывают периметр, но безопасность — это слои. Главные угрозы веб-приложений живут глубже: инъекции (когда непроверенные данные попадают в SQL или команды), некорректная аутентификация, утечки чувствительных данных в логах и ответах. Ни один middleware не спасёт от того, что ты сам подставил req.body в запрос к базе без валидации. Поэтому относись к трём базовым пакетам как к первому слою, а не к финальному рубежу: за ними обязаны идти строгая валидация любого ввода, параметризованные запросы через ORM, минимизация данных в ответах и аккуратное обращение с секретами. Безопасность — это привычка на каждом маршруте, а не одна строка в начале файла.