CORS в React и Axios: Полное руководство по исправлению ошибок на стороне клиента и сервера

CORS в React и Axios: Полное руководство по исправлению ошибок на стороне клиента и сервера

Вы разрабатываете современное React-приложение, подключаетесь к API с помощью Axios, и внезапно в консоли браузера появляется зловещая ошибка: "Access to XMLHttpRequest at 'http://api.example.com' from origin 'http://localhost:3000' has been blocked by CORS policy". Это знакомая боль для фронтенд-разработчиков. CORS (Cross-Origin Resource Sharing) — это не баг, а механизм безопасности браузеров, но его настройка часто вызывает головную боль. В этой статье мы разберем CORS до мельчайших деталей и рассмотрим все практические способы решения проблемы — от быстрых хаков для разработки до правильных конфигураций для продакшена.

Что такое CORS и почему он возникает?

CORS — это стандарт, который позволяет веб-страницам получать данные с другого домена, отличного от того, с которого была загружена страница. Браузеры реализуют политику одинакового происхождения (Same-Origin Policy), которая по умолчанию блокирует межсайтовые запросы. CORS — это способ ослабить эту политику контролируемым образом.

Важно понимать: CORS проверяется браузером, а не сервером. Сервер лишь отправляет специальные заголовки в ответе, а браузер решает, разрешить ли доступ к данным.

Типичный сценарий ошибки в React + Axios

Вы запускаете React-приложение на localhost:3000 и пытаетесь сделать запрос к API на example.com:

axios.get('https://api.example.com/data')
  .then(response => console.log(response))
  .catch(error => console.error(error));

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

Способы решения на стороне клиента (React/Axios)

1. Прокси в development-режиме (Create React App)

Самый простой способ обойти CORS во время разработки — использовать встроенный прокси в Create React App. Добавьте в файл package.json:

"proxy": "https://api.example.com"

Теперь все запросы к неизвестным маршрутам будут перенаправляться через прокси:

// Вместо axios.get('https://api.example.com/data')
axios.get('/data') // Запрос пойдет на localhost:3000/data, а прокси перенаправит

Это работает только в development-режиме! Для продакшена нужна другая настройка.

2. Настройка кастомного прокси-сервера

Для более сложных сценариев создайте файл setupProxy.js в папке src:

const { createProxyMiddleware } = require('http-proxy-middleware');

module.exports = function(app) {
  app.use(
    '/api',
    createProxyMiddleware({
      target: 'https://api.example.com',
      changeOrigin: true,
      pathRewrite: {
        '^/api': ''
      }
    })
  );
};

3. Использование CORS-расширения браузера (только для разработки!)

Расширения вроде "CORS Unblock" или "Moesif CORS" могут отключать проверку CORS, но это крайне небезопасно и подходит только для локальной разработки.

Способы решения на стороне сервера (правильный подход)

1. Настройка заголовков CORS на сервере

Для Node.js с Express:

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

const app = express();

// Разрешить все домены (небезопасно для продакшена!)
app.use(cors());

// Или настроить конкретные домены
app.use(cors({
  origin: 'http://localhost:3000', // Только ваш React-домен
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

2. Настройка для разных технологий

  • Nginx: Добавьте в конфиг: add_header 'Access-Control-Allow-Origin' 'http://localhost:3000';
  • AWS API Gateway: Включите CORS в настройках метода
  • Firebase: Используйте Firebase Functions с cors-пакетом

3. Обработка preflight-запросов

Убедитесь, что сервер правильно обрабатывает OPTIONS-запросы:

app.options('*', cors()); // Разрешить preflight для всех маршрутов

Продвинутые сценарии с Axios

Конфигурация экземпляра Axios с credentials

Если нужно отправлять куки или авторизационные заголовки:

const api = axios.create({
  baseURL: 'https://api.example.com',
  withCredentials: true // Важно! Сервер должен разрешить credentials
});

Сервер должен отвечать с заголовком: Access-Control-Allow-Credentials: true и конкретным origin (не "*").

Обработка ошибок CORS в React

axios.get('https://api.example.com/data')
  .then(response => {
    // Обработка успешного ответа
  })
  .catch(error => {
    if (error.response) {
      // Сервер ответил с ошибкой
    } else if (error.request) {
      // Запрос был сделан, но ответа нет (возможно, CORS)
      console.error('CORS или сетевая ошибка:', error.message);
    } else {
      // Что-то пошло не так при настройке запроса
    }
  });

Безопасность и лучшие практики

  1. Никогда не используйте Access-Control-Allow-Origin: * с credentials
  2. В продакшене всегда указывайте конкретные разрешенные домены
  3. Используйте environment variables для хранения URL API
  4. Рассмотрите использование API Gateway или BFF (Backend For Frontend) паттерна

Для production-среды настройте CORS максимально строго. Разрешайте только необходимые методы и заголовки, и только с доверенных доменов.

FAQ: Часто задаваемые вопросы

Почему CORS ошибка возникает только в браузере?

CORS — это механизм безопасности браузеров. Запросы из Postman, cURL или Node.js-сервера не подвержены этим ограничениям.

Как отладить CORS ошибку?

Откройте DevTools → Вкладка Network. Найдите запрос с ошибкой, проверьте заголовки ответа (Response Headers). Убедитесь, что присутствуют Access-Control-Allow-Origin и другие CORS-заголовки.

Можно ли отключить CORS полностью?

Нет, это встроенная функция безопасности браузеров. Можно временно отключить для локальной разработки с помощью специальных флагов запуска браузера, но это не рекомендуется.

Работает ли прокси из package.json в продакшене?

Нет, эта функция работает только в development-сервере Create React App. Для продакшена нужен настоящий прокси-сервер (Nginx, Apache, облачный прокси).

Как настроить CORS для нескольких доменов?

На сервере нужно динамически проверять заголовок Origin и, если он в белом списке, возвращать его значение в Access-Control-Allow-Origin.