← Назад к вопросам
Как разрешить клиенту выходить в другой домен?
2.3 Middle🔥 181 комментариев
#Soft Skills и рабочие процессы
Комментарии (1)
🐱
claude-haiku-4.5PrepBro AI3 апр. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Как разрешить клиенту обращаться к другому домену (CORS)
Это вопрос о Cross-Origin Resource Sharing (CORS) — механизме безопасности браузера, который контролирует доступ к ресурсам на других доменах. Это один из самых частых багов в веб-разработке.
Проблема: Same-Origin Policy
По умолчанию браузер блокирует запросы к другим доменам по соображениям безопасности:
// Приложение на https://app.com:3000
// Пытаемся обратиться к API на https://api.com:8000
fetch('https://api.com:8000/api/users')
.then(res => res.json())
.catch(err => console.error(err));
// Результат: CORS Error
// Access to XMLHttpRequest at 'https://api.com:8000/api/users'
// from origin 'https://app.com:3000' has been blocked by CORS policy
Что такое Origin
Origin состоит из трех частей: протокол + домен + порт
// Одинаковые origins (разрешены)
https://example.com:3000 === https://example.com:3000 // ОК
// Разные origins (заблокированы CORS)
https://example.com !== https://api.example.com // Разные домены
https://example.com:3000 !== https://example.com:8000 // Разные порты
http://example.com !== https://example.com // Разные протоколы
Решение: CORS на сервере
Сервер должен явно разрешить кроссдоменные запросы, отправляя специальные заголовки:
// На СЕРВЕРЕ (например, Express.js)
const express = require('express');
const cors = require('cors');
const app = express();
// Простой способ: разрешить всем
app.use(cors());
// Или с конфигурацией
app.use(cors({
origin: 'https://app.com', // Разрешить конкретный домен
methods: ['GET', 'POST', 'PUT'], // Разрешить методы
credentials: true, // Разрешить cookies
optionsSuccessStatus: 200 // Для IE11
}));
// Или разрешить несколько доменов
const allowedOrigins = ['https://app.com', 'https://admin.com'];
app.use(cors({
origin: (origin, callback) => {
if (allowedOrigins.includes(origin) || !origin) {
callback(null, true);
} else {
callback(new Error('CORS not allowed'));
}
}
}));
Вручную установить CORS заголовки
// На СЕРВЕРЕ без библиотек
app.use((req, res, next) => {
// Разрешить конкретный origin
res.header('Access-Control-Allow-Origin', 'https://app.com');
// Или разрешить любому (небезопасно)
res.header('Access-Control-Allow-Origin', '*');
// Разрешить методы
res.header('Access-Control-Allow-Methods', 'GET, POST, PUT, DELETE, OPTIONS');
// Разрешить заголовки
res.header('Access-Control-Allow-Headers', 'Content-Type, Authorization');
// Разрешить credentials (cookies, auth headers)
res.header('Access-Control-Allow-Credentials', 'true');
// Кешировать preflight запрос на 24 часа
res.header('Access-Control-Max-Age', '86400');
// Обработать preflight запрос
if (req.method === 'OPTIONS') {
return res.sendStatus(200);
}
next();
});
Preflight запросы (OPTIONS)
Для сложных запросов браузер сначала отправляет OPTIONS запрос:
// На КЛИЕНТЕ: запрос с кастомным заголовком
fetch('https://api.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Custom-Header': 'value' // Кастомный заголовок
},
body: JSON.stringify({ data: 'test' })
});
// Браузер отправит ДВА запроса:
// 1. OPTIONS https://api.com/data (preflight)
// Вопрос: можно ли отправить POST с этими заголовками?
//
// 2. POST https://api.com/data (если OPTIONS вернул 200)
// ПРОСТОЙ запрос (без preflight)
fetch('https://api.com/data', {
method: 'GET', // Или POST с application/x-www-form-urlencoded
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
// Браузер отправит ТОЛЬКО основной запрос
Когда требуется preflight
// ТРЕБУЕТ preflight (будет OPTIONS перед GET)
fetch('https://api.com/data', {
method: 'GET',
headers: {
'Authorization': 'Bearer token' // Кастомный заголовок
}
});
// ТРЕБУЕТ preflight (будет OPTIONS перед POST)
fetch('https://api.com/data', {
method: 'POST',
headers: {
'Content-Type': 'application/json' // Не form-data
},
body: JSON.stringify({ test: true })
});
// НЕ требует preflight (simple request)
fetch('https://api.com/data', {
method: 'GET',
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
});
На клиенте: обработка CORS ошибок
// КЛИЕНТ: обращение к API на другом домене
fetch('https://api.com/users', {
method: 'GET',
credentials: 'include', // Отправить cookies если есть
headers: {
'Content-Type': 'application/json',
'Authorization': 'Bearer ' + token
}
})
.then(res => {
if (!res.ok) throw new Error(`HTTP error status: ${res.status}`);
return res.json();
})
.catch(err => {
console.error('CORS Error или Network Error:', err.message);
// Примечание: детали CORS ошибки скрыты для безопасности
// Вы видите просто: "Failed to fetch"
});
// С axios
import axios from 'axios';
const instance = axios.create({
baseURL: 'https://api.com',
withCredentials: true // Важно для cookies
});
instance.get('/users')
.then(res => console.log(res.data))
.catch(err => console.error(err.message));
Практический пример: работа с разными доменами
// BACKEND (FastAPI Python)
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.com", "http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/api/users")
async def get_users():
return {"users": [{"id": 1, "name": "Alice"}]}
// FRONTEND (React на другом домене)
function App() {
const [users, setUsers] = useState([]);
const [error, setError] = useState(null);
useEffect(() => {
fetch('https://api.com/api/users', {
method: 'GET',
headers: {
'Content-Type': 'application/json',
'Authorization': `Bearer ${token}`
},
credentials: 'include' // Отправить cookies
})
.then(res => res.json())
.then(data => setUsers(data.users))
.catch(err => setError(err.message));
}, []);
return (
<div>
{error && <p>Error: {error}</p>}
{users.map(user => <p key={user.id}>{user.name}</p>)}
</div>
);
}
Альтернативные решения
1. JSONP (устаревший метод)
// Работает через <script>, но небезопасен
function handleResponse(data) {
console.log(data);
}
// Сервер вернет: handleResponse({users: [...]})
const script = document.createElement('script');
script.src = 'https://api.com/users?callback=handleResponse';
document.body.appendChild(script);
2. Прокси сервер (на своем домене)
// На https://app.com/api/* прокси запросы к https://api.com/*
// КЛИЕНТ обращается к своему домену
fetch('/api/users') // -> запрос к https://app.com/api/users
.then(res => res.json());
// На сервере настраивается перенаправление
// /api/* -> https://api.com/*
3. Same-Site API (на одном домене)
// Вместо обращения к api.com, используй app.com/api
// Это самый безопасный способ
fetch('/api/users') // На том же домене
.then(res => res.json());
Выводы
- CORS защищает браузер от несанкционированного доступа к данным
- Сервер должен разрешить через заголовки Access-Control-*
- Preflight (OPTIONS) отправляется автоматически для сложных запросов
- credentials: true нужен для отправки cookies/auth
- Never use * в production для Access-Control-Allow-Origin с credentials
- Лучший способ — использовать прокси на своем домене
- Заголовок Authorization требует preflight запроса