CORS для JavaScript (Node.js): полное руководство с примерами

Онлайн-тренажер Python для начинающих

Изучайте Python легко и без перегрузки теорией. Решайте практические задачи с автоматической проверкой, получайте подсказки на русском языке и пишите код прямо в браузере — без необходимости что-либо устанавливать.

Начать курс

Библиотека CORS для JavaScript (Node.js): Полное руководство

1. Что это и зачем нужна

CORS (Cross-Origin Resource Sharing) — это механизм безопасности, реализованный в браузерах, который контролирует доступ к ресурсам (API, шрифтам, скриптам) с веб-страниц, загруженных с другого домена (origin). По умолчанию браузеры блокируют HTTP-запросы, инициированные скриптами (например, через fetch или XMLHttpRequest), если они направлены на домен, отличный от того, с которого была загружена страница. Это предотвращает атаки типа CSRF (Cross-Site Request Forgery) и утечку данных.

Библиотека cors для Node.js и Express — это middleware (промежуточное ПО), которое позволяет разработчикам легко настраивать и управлять CORS-политиками на серверной стороне. Вместо ручного написания заголовков (Access-Control-Allow-Origin, Access-Control-Allow-Methods и т.д.) и обработки предварительных запросов (preflight requests с методом OPTIONS), эта библиотека автоматизирует весь процесс. Она интегрируется в цепочку обработки запросов Express и добавляет необходимые заголовки к ответам.

Зачем это нужно? Если вы разрабатываете API на Node.js, которое будет использоваться фронтенд-приложением (React, Vue, Angular), мобильным приложением или сторонним сервисом, работающим с другого домена (например, api.mysite.com для app.mysite.com), вам обязательно нужно настроить CORS. Без этого браузер просто заблокирует запросы, и пользователь увидит ошибку в консоли. Библиотека cors делает эту настройку гибкой, безопасной и простой.

2. Установка

Установка производится через менеджер пакетов npm (Node Package Manager). Убедитесь, что у вас установлен Node.js и npm.

npm install cors

После установки пакет будет добавлен в node_modules, а запись о нем появится в файле package.json.

Если вы используете yarn:

yarn add cors

3. Быстрый старт — минимальный рабочий пример

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

const express = require('express');
const cors = require('cors');

const app = express();

// Включаем CORS для всех маршрутов
app.use(cors());

app.get('/api/data', (req, res) => {
    res.json({ message: 'Этот ответ разрешен для всех источников!' });
});

app.listen(3000, () => {
    console.log('Сервер запущен на порту 3000');
});

В этом примере любой запрос с любого домена (например, с http://localhost:8080 или https://example.com) к /api/data будет успешно обработан. Браузер не заблокирует ответ.

4. Основные методы/функции/классы

Библиотека cors предоставляет одну основную функцию — cors(options), которая возвращает middleware-функцию. Однако, настройки передаются через объект options, что дает огромную гибкость. Рассмотрим 8 ключевых опций и методов.

4.1. cors() — базовая конфигурация (все разрешено)

Сигнатура: cors(options?: CorsOptions)

Что делает: Создает middleware, который добавляет заголовки CORS к ответу. Без аргументов разрешает доступ с любого источника (*), любые методы и заголовки.

const cors = require('cors');
// Разрешаем все
app.use(cors());

4.2. origin — настройка разрешенных источников

Сигнатура: origin: string | string[] | RegExp | Function

Что делает: Определяет, какие домены могут получать доступ к ресурсам. Можно указать конкретный домен, список доменов, регулярное выражение или функцию для динамической проверки.

// Разрешаем только один домен
app.use(cors({ origin: 'https://myfrontend.com' }));

// Разрешаем несколько доменов
app.use(cors({ origin: ['https://site1.com', 'https://site2.com'] }));

// Разрешаем все поддомены example.com
app.use(cors({ origin: /\.example\.com$/ }));

// Динамическая проверка (например, из базы данных)
const whitelist = ['https://site1.com', 'https://site2.com'];
app.use(cors({
    origin: function (origin, callback) {
        if (whitelist.indexOf(origin) !== -1 || !origin) {
            callback(null, true);
        } else {
            callback(new Error('Not allowed by CORS'));
        }
    }
}));

4.3. methods — разрешенные HTTP-методы

Сигнатура: methods: string | string[]

Что делает: Определяет, какие HTTP-методы (GET, POST, PUT, DELETE, PATCH и т.д.) разрешены для кросс-доменных запросов. По умолчанию: GET, HEAD, PUT, PATCH, POST, DELETE.

// Разрешаем только GET и POST
app.use(cors({ methods: ['GET', 'POST'] }));

// Строковый формат
app.use(cors({ methods: 'GET,POST,PUT' }));

4.4. allowedHeaders — разрешенные заголовки

Сигнатура: allowedHeaders: string | string[]

Что делает: Определяет, какие HTTP-заголовки могут быть отправлены в запросе (например, Content-Type, Authorization). По умолчанию разрешены стандартные заголовки.

// Разрешаем кастомные заголовки
app.use(cors({
    allowedHeaders: ['Content-Type', 'Authorization', 'X-Custom-Header']
}));

4.5. exposedHeaders — заголовки, доступные клиенту

Сигнатура: exposedHeaders: string | string[]

Что делает: Определяет, какие заголовки ответа могут быть прочитаны клиентским JavaScript-кодом (через response.headers). По умолчанию доступны только стандартные заголовки.

// Делаем заголовок X-Total-Count доступным для клиента
app.use(cors({
    exposedHeaders: ['X-Total-Count', 'Content-Range']
}));

4.6. credentials — поддержка кук и авторизации

Сигнатура: credentials: boolean

Что делает: Если установлено в true, сервер разрешает отправку кук, HTTP-аутентификации и TLS-сертификатов клиента. Важно: При credentials: true опция origin не может быть '*', нужно указать конкретный домен.

// Разрешаем куки с конкретного домена
app.use(cors({
    origin: 'https://myfrontend.com',
    credentials: true
}));

4.7. maxAge — кэширование предварительных запросов

Сигнатура: maxAge: number

Что делает: Указывает браузеру, сколько секунд (время кэширования) можно не отправлять предварительный запрос (preflight) для данного URL. Уменьшает количество запросов к серверу.

// Кэшируем preflight на 24 часа (86400 секунд)
app.use(cors({
    maxAge: 86400
}));

4.8. optionsSuccessStatus — статус для preflight-запросов

Сигнатура: optionsSuccessStatus: number

Что делает: Определяет HTTP-статус, который возвращается на предварительные запросы (метод OPTIONS). По умолчанию — 204 (No Content). Некоторые старые браузеры или прокси могут требовать 200.

// Используем статус 200 для совместимости
app.use(cors({
    optionsSuccessStatus: 200
}));

5. Реальный практический пример (100-200 строк кода)

Создадим полноценный REST API для управления списком задач (To-Do list) с гибкой настройкой CORS. API будет поддерживать аутентификацию через JWT и разрешать доступ только определенным доменам.

const express = require('express');
const cors = require('cors');
const jwt = require('jsonwebtoken'); // Для примера аутентификации

const app = express();
const PORT = 3000;
const SECRET_KEY = 'your-secret-key-change-in-production';

// --- Настройка CORS ---
const corsOptions = {
    // Разрешаем только наш фронтенд и локальную разработку
    origin: function (origin, callback) {
        const allowedOrigins = [
            'https://my-todo-app.com',
            'http://localhost:3001', // Для разработки
            'http://127.0.0.1:5500'  // Для Live Server
        ];
        // Разрешаем запросы без origin (например, из Postman или серверных скриптов)
        if (!origin || allowedOrigins.indexOf(origin) !== -1) {
            callback(null, true);
        } else {
            callback(new Error('Доступ запрещен политикой CORS'));
        }
    },
    methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
    allowedHeaders: ['Content-Type', 'Authorization'],
    exposedHeaders: ['X-Total-Count'],
    credentials: true, // Разрешаем куки и JWT в заголовках
    maxAge: 3600 // Кэшируем preflight на 1 час
};

app.use(cors(corsOptions));

// --- Middleware для парсинга JSON ---
app.use(express.json());

// --- Хранилище задач (в реальном проекте — база данных) ---
let tasks = [
    { id: 1, title: 'Изучить CORS', completed: false },
    { id: 2, title: 'Написать документацию', completed: true }
];
let nextId = 3;

// --- Middleware для проверки JWT (упрощенный) ---
function authenticateToken(req, res, next) {
    const authHeader = req.headers['authorization'];
    const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN

    if (token == null) return res.sendStatus(401);

    jwt.verify(token, SECRET_KEY, (err, user) => {
        if (err) return res.sendStatus(403);
        req.user = user;
        next();
    });
}

// --- Маршруты API ---

// Получить все задачи (публичный endpoint)
app.get('/api/tasks', (req, res) => {
    res.setHeader('X-Total-Count', tasks.length);
    res.json(tasks);
});

// Получить задачу по ID
app.get('/api/tasks/:id', (req, res) => {
    const task = tasks.find(t => t.id === parseInt(req.params.id));
    if (!task) return res.status(404).json({ error: 'Задача не найдена' });
    res.json(task);
});

// Создать новую задачу (требует аутентификации)
app.post('/api/tasks', authenticateToken, (req, res) => {
    const { title } = req.body;
    if (!title) return res.status(400).json({ error: 'Название задачи обязательно' });

    const newTask = {
        id: nextId++,
        title: title,
        completed: false,
        userId: req.user.id // Из JWT
    };
    tasks.push(newTask);
    res.status(201).json(newTask);
});

// Обновить задачу
app.put('/api/tasks/:id', authenticateToken, (req, res) => {
    const task = tasks.find(t => t.id === parseInt(req.params.id));
    if (!task) return res.status(404).json({ error: 'Задача не найдена' });

    const { title, completed } = req.body;
    if (title !== undefined) task.title = title;
    if (completed !== undefined) task.completed = completed;

    res.json(task);
});

// Удалить задачу
app.delete('/api/tasks/:id', authenticateToken, (req, res) => {
    const index = tasks.findIndex(t => t.id === parseInt(req.params.id));
    if (index === -1) return res.status(404).json({ error: 'Задача не найдена' });

    tasks.splice(index, 1);
    res.status(204).send();
});

// --- Обработка ошибок CORS (если origin не разрешен) ---
app.use((err, req, res, next) => {
    if (err.message === 'Доступ запрещен политикой CORS') {
        res.status(403).json({ error: err.message });
    } else {
        next(err);
    }
});

// --- Запуск сервера ---
app.listen(PORT, () => {
    console.log(`Сервер запущен на http://localhost:${PORT}`);
    console.log('CORS настроен для: https://my-todo-app.com и localhost:3001');
});

// --- Пример получения JWT (для теста) ---
app.post('/api/login', (req, res) => {
    const { username } = req                    

Рекомендаций