CORS (Cross-Origin Resource Sharing)
CORS — механизм браузера, позволяющий серверу разрешать cross-origin запросы через HTTP-заголовки; это не защита сервера от запросов, а контроль того, какие ответы браузер передаёт скрипту — понимание этого отличает junior от senior и тема обязательно поднимается при разборе fetch-ошибок.
CORS Preflight Flow Browser origin: https://app.example.com | | 1. OPTIONS /api/data <- Preflight (перед «опасным» запросом) | Origin: https://app.example.com | Access-Control-Request-Method: POST | Access-Control-Request-Headers: Content-Type, X-Custom-Header | v Server api.example.com | 2. 204 No Content | Access-Control-Allow-Origin: https://app.example.com | Access-Control-Allow-Methods: GET, POST, DELETE | Access-Control-Allow-Headers: Content-Type, X-Custom-Header | Access-Control-Max-Age: 86400 (кэш preflight, сек) | v Browser — preflight OK -> отправляет настоящий запрос | | 3. POST /api/data | Origin: https://app.example.com | Content-Type: application/json | v Server | 4. 200 OK + Access-Control-Allow-Origin: https://app.example.com v Browser — заголовок совпадает -> передаёт ответ скрипту Simple requests (GET/POST + safe headers + form CT) — preflight НЕ нужен! Триггеры preflight: PUT/PATCH/DELETE, кастомные заголовки, JSON Content-Type
// Server-side CORS middleware (Node.js / Express)app.use((req, res, next) => { const ALLOWED = new Set([ 'https://app.example.com', 'https://staging.example.com', ]); const origin = req.headers.origin;
if (ALLOWED.has(origin)) { res.setHeader('Access-Control-Allow-Origin', origin); // конкретный origin, не '*' res.setHeader('Vary', 'Origin'); // кэш должен учитывать Origin } res.setHeader('Access-Control-Allow-Methods', 'GET,POST,PUT,PATCH,DELETE,OPTIONS'); res.setHeader('Access-Control-Allow-Headers', 'Content-Type,Authorization,X-Request-Id'); res.setHeader('Access-Control-Max-Age', '86400'); // preflight кэш на 24 ч
if (req.method === 'OPTIONS') return res.sendStatus(204); next();});
// Exposed headers: по умолчанию JS видит только «safe» заголовки ответаres.setHeader('Access-Control-Expose-Headers', 'X-RateLimit-Remaining, X-Request-Id');// Теперь в браузере: response.headers.get('X-RateLimit-Remaining') !== null// Client: credentials и ограничения wildcardfetch('https://api.example.com/data', { credentials: 'include', // отправляет cookies cross-origin});
// Сервер ОБЯЗАН ответить конкретным origin (не '*') + credentials:// Access-Control-Allow-Origin: https://app.example.com (НЕ *)// Access-Control-Allow-Credentials: true
// Проверка: CORS не защищает сервер!// curl -H 'Origin: https://evil.com' https://api.example.com/data// → сервер всё равно обработает запрос; CORS блокирует только доступ к ответу в браузере.// Для защиты данных нужна авторизация на сервере.
// CORS и preflight-кэш// Access-Control-Max-Age: 86400 → браузер не будет делать preflight 24 ч// Максимум в Chrome: 7200 с (2 ч), Firefox: 86400 с — зависит от браузера// 0 → отключить кэш (принудительный preflight на каждый запрос)Итог: CORS проверяется браузером, управляется заголовками сервера; preflight нужен для non-simple запросов; credentials требуют явного разрешения с обеих сторон.