Серверное middleware, валидация и безопасность

Серверные данные нельзя доверять клиенту: валидация, аккуратные ошибки и секреты в runtimeConfig — обязательная гигиена эндпоинтов.
Суть: файлы в server/middleware/ выполняются перед каждым серверным запросом. Входные данные нужно валидировать, ошибки отдавать через createError с правильным статусом, а секреты держать в runtimeConfig, а не в клиентском коде.

Серверный код мощен и потому требует дисциплины. Любые данные от клиента — тело, параметры, заголовки — потенциально вредоносны: их можно подделать. Бэкенд обязан проверять вход, не доверять токенам «на слово» и прятать секреты. Этот урок — про гигиену серверной части.

Серверное middleware живёт в server/middleware/ и срабатывает перед каждым запросом — удобно для логирования, аутентификации, CORS:

// server/middleware/auth.ts
export default defineEventHandler((event) => {
  const token = getHeader(event, "authorization")
  if (event.path.startsWith("/api/admin") && !isValid(token)) {
    throw createError({ statusCode: 401, statusMessage: "Не авторизован" })
  }
  // ничего не возвращаем -> запрос идёт дальше
})

Валидация входа — не опция, а необходимость. Прежде чем сохранять или использовать данные, проверьте их форму и содержимое; неверный вход — это 400 Bad Request. Секреты (ключи API, строки подключения к БД) держат в runtimeConfig и читают на сервере через useRuntimeConfig() — так они не утекут в бандл.

   Конвейер серверного запроса

   запрос -> [server/middleware] -> обработчик server/api
                |                        |
          (auth, log, CORS)        валидация входа
                |                        |
          401/403 при отказе       400 при плохих данных
                                         |
                                   секреты из runtimeConfig

Как работает под капотом

Серверное middleware регистрируется в Nitro как глобальный обработчик, выполняемый до маршрутных. Если оно бросает createError, цепочка прерывается и клиент получает ответ с этим статусом. runtimeConfig разделён на серверную часть (приватную) и public (доступную клиенту): приватные значения подставляются только в серверный бандл. Валидация — это ваша логика; популярны схемы (например, через библиотеки валидации), но суть одна: не пускать дальше кривые данные.

Смоделируем конвейер «middleware + валидация» с понятными статусами ответа:

// Серверный конвейер: авторизация -> валидация -> обработка.
function createError(status, msg) { return { error: true, status, msg }; }

function authMiddleware(req) {
  if (req.path.startsWith("/api/admin") && req.token !== "secret") {
    return createError(401, "Не авторизован");
  }
  return null;                          // пропускаем
}

function validate(body) {
  if (!body || typeof body.name !== "string" || body.name.length === 0) {
    return createError(400, "Поле name обязательно");
  }
  return null;
}

function handleRequest(req) {
  const authErr = authMiddleware(req);
  if (authErr) return authErr;
  const valErr = validate(req.body);
  if (valErr) return valErr;
  return { status: 201, created: req.body.name };
}

console.log(handleRequest({ path: "/api/admin/x", token: "wrong", body: {} }));
console.log(handleRequest({ path: "/api/products", body: { name: "" } }));
console.log(handleRequest({ path: "/api/products", body: { name: "Книга" } }));

Попробуй сам ▶ — запрос проходит охрану и валидацию, и только корректный доходит до обработки с кодом 201. Кривой вход останавливается на 400, чужак — на 401.

Частые ошибки

  • Доверять клиентским данным. Тело и заголовки подделываются — всегда валидируйте на сервере.
  • Секреты в public или в клиентском коде. Приватные ключи — только в серверной части runtimeConfig.
  • Возвращать 200 на ошибку. Используйте корректные статусы: 400, 401, 403, 404, 500 — это контракт API.

Best practices

  • Валидируйте каждый вход; на нарушение — 400 с понятным сообщением.
  • Авторизацию и сквозные проверки выносите в server/middleware.
  • Секреты — в приватный runtimeConfig, читать через useRuntimeConfig() на сервере.

Итог: серверная безопасность — это валидация входа, корректные статусы и секреты в runtimeConfig. Middleware централизует сквозные проверки. На этом раздел о бэкенде завершён — переходим к финалу: SEO и деплою.

Проверьте себя
1. Где в Nuxt следует хранить приватные секреты вроде ключей API и строк подключения к БД?
AВ public-части runtimeConfig
BВ приватной (серверной) части runtimeConfig, читая через useRuntimeConfig() на сервере
CПрямо в компонентах страниц
DВ localStorage браузера
2. Почему серверный обработчик обязан валидировать тело и параметры запроса?
AЧтобы ускорить ответ
BПотому что данные приходят от клиента и могут быть подделаны — без проверки это дыра в безопасности
CВалидация нужна только в dev-режиме
DЭто требование лицензии Nuxt