Backend Typescript 1.0.0 Help

CORS

Что такое CORS и зачем он нужен

CORS (Cross-Origin Resource Sharing) — это механизм безопасности браузера, который контролирует, может ли страница с одного источника (схема+домен+порт) обращаться к ресурсам другого источника. По умолчанию браузер ограничивает доступ из скриптов к ответам с другого источника. CORS позволяет серверу явно указать, кому и что можно запрашивать, с помощью специальных заголовков.

  • Origin — комбинация схемы (http/https), домена и порта. Разные порты уже разные источники.

  • Браузер автоматически применяет правила CORS; для запросов с бекенда (сервер->сервер) CORS не требуется.

  • Сервер решает, какие источники допустить, какими методами и с какими заголовками.

Типы запросов и preflight (предварительный) запрос

Браузер различает простые запросы и запросы, требующие предварительной проверки (preflight). Preflight посылается методом OPTIONS и спрашивает у сервера разрешение на основной запрос.

  • Простые запросы: методы GET/HEAD/POST, заголовки только из безопасного набора (например, Accept, Content-Type: application/x-www-form-urlencoded | text/plain | multipart/form-data), без нестандартных заголовков.

  • Не простые: любые другие методы (PUT, PATCH, DELETE), кастомные заголовки (например, X-Request-Id), Content-Type c application/json — всё это требует preflight.

Ключевые CORS-заголовки и их смысл

  • Access-Control-Allow-Origin — кому разрешено. Значение: конкретный origin (например, https://app.example.com) или * (всем).

  • Access-Control-Allow-Methods — какие методы разрешены (GET, POST, PUT, DELETE, ...).

  • Access-Control-Allow-Headers — какие запросные заголовки разрешены (например, Content-Type, Authorization).

  • Access-Control-Allow-Credentials — можно ли отправлять куки/HTTP-авторизацию (true/false).

  • Access-Control-Expose-Headers — какие заголовки ответа будут доступны JS (по умолчанию доступ к большинству заголовков закрыт).

  • Access-Control-Max-Age — сколько секунд кешировать результат preflight, чтобы реже делать OPTIONS.

  • Vary: Origin — сообщает кешам, что ответ зависит от заголовка Origin.

Быстрый старт: пакет cors

Проще всего управлять CORS через официальный пакет cors для Express.

// src/app.ts (фрагмент) import express from "express"; import cors from "cors"; const app = express(); app.use(cors({ origin: "https://app.example.com", // конкретный фронтенд methods: ["GET", "POST", "PUT", "PATCH", "DELETE"], allowedHeaders: ["Content-Type", "Authorization", "X-Request-Id"], exposedHeaders: ["X-Total-Count", "X-Request-Id"], credentials: true, // разрешить куки/авторизацию maxAge: 600 // кэш preflight на 10 минут })); app.get("/health", (_req, res) => res.send("ok"));

Whitelist и динамический origin

// src/middlewares/cors.ts import cors from "cors"; const whitelist = new Set([ "https://app.example.com", "https://admin.example.com", "http://localhost:5173" ]); export const corsMiddleware = cors({ origin: (origin, callback) => { // origin может быть undefined для некоторых инструментов и curl if (!origin || whitelist.has(origin)) { callback(null, true); } else { callback(new Error("Not allowed by CORS")); } }, credentials: true, methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"], allowedHeaders: ["Content-Type", "Authorization", "X-Request-Id"], exposedHeaders: ["X-Total-Count", "X-Request-Id"], maxAge: 600 }); // src/app.ts import express from "express"; import {corsMiddleware} from "./middlewares/cors"; const app = express(); app.use(corsMiddleware);

Credentials (куки, заголовки авторизации) безопасно

Чтобы браузер отправлял и принимал куки/токены между источниками, нужны настройки и на клиенте, и на сервере.

  • Сервер: credentials: true и Access-Control-Allow-Origin не может быть *.

  • Клиент: для fetch/axios указывается credentials: "include".

  • Куки: настройте SameSite=None; Secure (иначе межсайтовая передача будет заблокирована современными браузерами).

  • Только HTTPS для SameSite=None; Secure.

// пример установки cookie в Express res.cookie("session", token, { httpOnly: true, secure: true, // обязательно в продакшн с HTTPS sameSite: "none", // разрешить межсайтовую отправку domain: ".example.com", // опционально: общий домен path: "/" });

Ручная настройка без пакета cors (понимание механики)

Иногда полезно явно увидеть ответы на preflight и основной запрос.

// src/app.ts (минимальный пример) import express from "express"; const app = express(); const ALLOWED_ORIGIN = "https://app.example.com"; app.use((req, res, next) => { res.setHeader("Access-Control-Allow-Origin", ALLOWED_ORIGIN); res.setHeader("Vary", "Origin"); res.setHeader("Access-Control-Allow-Credentials", "true"); res.setHeader("Access-Control-Expose-Headers", "X-Total-Count, X-Request-Id"); if (req.method === "OPTIONS") { res.setHeader("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,OPTIONS"); res.setHeader("Access-Control-Allow-Headers", req.header("Access-Control-Request-Headers") || "Content-Type, Authorization"); res.setHeader("Access-Control-Max-Age", "600"); return res.status(204).send(); } next(); }); app.get("/api/data", (_req, res) => { res.setHeader("X-Request-Id", "abc-123"); res.json({ok: true}); });

Оптимизация preflight (производительность)

  • Используйте Access-Control-Max-Age для кеширования разрешения браузером (например, 600–3600 секунд).

  • Сведите к минимуму нестандартные заголовки и меняйте их только при необходимости — меньше причин для preflight.

  • Стабилизируйте набор методов и заголовков — частые изменения могут приводить к лишним OPTIONS.

Безопасность: частые ошибки и как их избежать

  • Отражение Origin без валидации: установка Access-Control-Allow-Origin равным входящему Origin без whitelist — риск утечки данных. Всегда проверяйте против списка.

  • Allow-Credentials с *: браузер заблокирует такой ответ; используйте конкретный origin.

  • Слишком широкий Allow-Headers: разрешайте только нужные заголовки, чтобы сузить поверхность атаки.

  • Отсутствует Vary: Origin: кэши могут отдать ответ не тому источнику. Добавляйте Vary при динамическом разрешении origin.

  • HTTP вместо HTTPS: для SameSite=None куки должны быть Secure, то есть только HTTPS.

Отладка и диагностика

  • Смотрите вкладку "Network" в DevTools: есть ли запрос OPTIONS, какие заголовки пришли/ушли.

  • Проверьте консоль: браузер пишет причину блокировки CORS (какого заголовка не хватает).

  • Логируйте на сервере заголовки Origin, Access-Control-Request-Method, Access-Control-Request-Headers для preflight.

  • Тестируйте curl-ом серверную реакцию (curl не применяет CORS, но помогает увидеть заголовки ответа).

curl -i -X OPTIONS "https://api.example.com/resource" \ -H "Origin: https://app.example.com" \ -H "Access-Control-Request-Method: PUT" \ -H "Access-Control-Request-Headers: Content-Type, Authorization"

Dev proxy как альтернатива при локальной разработке

Чтобы реже трогать CORS в деве, используйте прокси в дев-сервере фронта (Vite/webpack). Тогда запросы уходят на тот же origin, а прокси перенаправляет их на API.

Краткий чек-лист настроек

  • Определите, какие фронтенды (origin) должны иметь доступ — сделайте whitelist.

  • Разрешите только нужные методы и заголовки (Allow-Methods, Allow-Headers).

  • Если нужны куки/авторизация — credentials: true, конкретный Allow-Origin, куки с SameSite=None; Secure.

  • Настройте Max-Age для кеширования preflight.

  • Добавьте Vary: Origin при динамическом разрешении.

Last modified: 01 October 2025