CORS (Cross-Origin Resource Sharing) — механизм браузерной безопасности, регулирующий доступ к ресурсам между разными источниками (origin). Источник включает:
Если frontend-приложение на Vite работает по адресу:
http://localhost:5173
а API расположен по адресу:
http://localhost:3000
браузер считает их разными источниками из-за различия портов. Любой запрос между ними попадает под действие политики CORS.
При отсутствии корректных CORS-заголовков браузер блокирует запросы независимо от того, успешно ли сервер обработал их на своей стороне.
Vite активно используется вместе с backend API, поэтому настройка CORS становится одной из наиболее частых задач при разработке.
Vite сам по себе не снимает ограничения браузера. Он лишь:
Проблема CORS решается:
server.corsVite предоставляет встроенную настройку:
export default {
server: {
cors: true
}
}
При включении этой опции dev-сервер добавляет CORS-заголовки к собственным ресурсам:
Access-Control-Allow-Origin: *
Это влияет на:
Однако данная настройка не исправляет CORS между frontend и внешним API.
Пример:
import { defineConfig } from 'vite'
export default defineConfig({
server: {
cors: true
}
})
Эквивалентная форма:
server: {
cors: {}
}
Vite использует middleware библиотеки cors под
капотом.
server.cors может принимать объект настроек:
import { defineConfig } from 'vite'
export default defineConfig({
server: {
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'POST'],
allowedHeaders: ['Content-Type'],
credentials: true
}
}
})
originРазрешённые источники:
cors: {
origin: 'http://localhost:3000'
}
Массив:
cors: {
origin: [
'http://localhost:3000',
'http://localhost:8080'
]
}
Регулярное выражение:
cors: {
origin: /localhost/
}
Функция:
cors: {
origin(origin, callback) {
if (!origin) {
callback(null, true)
return
}
if (origin.includes('localhost')) {
callback(null, true)
} else {
callback(new Error('Not allowed'))
}
}
}
Функциональная форма полезна при сложной политике безопасности.
methodsОпределяет допустимые HTTP-методы:
cors: {
methods: ['GET', 'POST', 'PUT', 'DELETE']
}
Если метод отсутствует в списке, браузер заблокирует запрос после preflight-проверки.
allowedHeadersУказывает разрешённые заголовки:
cors: {
allowedHeaders: [
'Content-Type',
'Authorization'
]
}
Часто требуется для JWT-аутентификации:
Authorization: Bearer token
credentialsРазрешает передачу:
Пример:
cors: {
credentials: true
}
В этом случае нельзя использовать:
Access-Control-Allow-Origin: *
Необходимо явно указать origin:
cors: {
origin: 'http://localhost:5173',
credentials: true
}
exposedHeadersПо умолчанию браузер скрывает часть заголовков ответа.
Разрешение:
cors: {
exposedHeaders: [
'X-Total-Count',
'X-Request-Id'
]
}
После этого frontend сможет читать:
response.headers.get('X-Total-Count')
maxAgeОпределяет время кеширования preflight-запросов:
cors: {
maxAge: 86400
}
Это уменьшает количество OPTIONS-запросов.
Перед некоторыми запросами браузер автоматически отправляет:
OPTIONS
Это называется preflight request.
Он используется при:
Content-Type: application/json;Пример:
OPTIONS /api/users
Сервер должен вернуть:
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
Иначе основной запрос не будет отправлен.
Preflight не выполняется для:
application/x-www-form-urlencoded;multipart/form-data;text/plain.Такие запросы называются simple requests.
server.cors не решает проблему APIЧастая ошибка:
server: {
cors: true
}
и ожидание, что запросы к backend заработают автоматически.
Но запрос:
fetch('http://localhost:3000/api')
обрабатывается backend-сервером, а не Vite.
Следовательно:
Наиболее популярный подход при разработке — proxy.
Пример:
import { defineConfig } from 'vite'
export default defineConfig({
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
})
Frontend:
fetch('/api/users')
Vite:
Для браузера запрос остаётся same-origin.
CORS полностью обходится.
Браузер работает только с origin Vite.
Не требуется изменять backend.
Frontend не знает реальный backend URL.
Все запросы идут через Vite.
changeOriginproxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
Изменяет заголовок:
Host
Это важно для:
rewriteПозволяет изменять путь запроса.
Пример:
proxy: {
'/api': {
target: 'http://localhost:3000',
rewrite: path => path.replace(/^\/api/, '')
}
}
Запрос:
/api/users
превратится в:
/users
proxy: {
'/api': {
target: 'https://localhost:8443',
secure: false
}
}
secure: false отключает проверку SSL-сертификата.
Используется для self-signed сертификатов в dev-среде.
Vite активно использует WebSocket для HMR.
Иногда reverse proxy или backend блокируют WS-соединения.
Настройка:
proxy: {
'/socket': {
target: 'ws://localhost:3000',
ws: true
}
}
Правильное место настройки CORS — backend.
import cors from 'cors'
import express from 'express'
const app = express()
app.use(cors({
origin: 'http://localhost:5173',
credentials: true
}))
import Fastify from 'fastify'
import cors from '@fastify/cors'
const app = Fastify()
await app.register(cors, {
origin: 'http://localhost:5173'
})
const app = await NestFactory.create(AppModule)
app.enableCors({
origin: 'http://localhost:5173'
})
return [
'paths' => ['api/*'],
'allowed_methods' => ['*'],
'allowed_origins' => ['http://localhost:5173'],
];
@Configuration
public class CorsConfig {
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("http://localhost:5173");
}
};
}
}
No 'Access-Control-Allow-Origin' headerНаиболее распространённая ошибка.
Причины:
Credentials flag is trueОшибка:
The value of the 'Access-Control-Allow-Origin' header
must not be '*'
Возникает при:
credentials: true
и одновременно:
Access-Control-Allow-Origin: *
Причины:
Allow-Headers;Allow-Methods.Если frontend работает через HTTPS:
https://localhost:5173
а API через HTTP:
http://localhost:3000
браузер блокирует запрос независимо от CORS.
Вкладка:
Network
Позволяет анализировать:
Особое внимание:
Access-Control-Allow-Origin
Access-Control-Allow-Credentials
Access-Control-Allow-Headers
Access-Control-Allow-Methods
Frontend:
fetch('/api/profile', {
credentials: 'include'
})
Backend:
Access-Control-Allow-Credentials: true
и:
Access-Control-Allow-Origin: http://localhost:5173
Cookie также должна иметь:
SameSite=None
Secure
для cross-site запросов.
При SSR часть запросов выполняется на сервере Node.js.
В этом случае ограничения браузера отсутствуют.
Пример:
const response = await fetch('http://api.internal/users')
Node.js не применяет browser CORS policy.
Однако после гидратации браузерные ограничения снова начинают действовать.
CORS:
CSRF:
CORS не защищает от CSRF.
В production Vite обычно:
Следовательно:
server.cors больше не используется;location /api {
add_header Access-Control-Allow-Origin https://example.com;
add_header Access-Control-Allow-Credentials true;
proxy_pass http://backend;
}
Плохая практика:
Access-Control-Allow-Origin: *
для приватного API.
Особенно опасно вместе с:
Access-Control-Allow-Credentials: true
Безопаснее:
http://localhost:5173
http://localhost:3000
server: {
proxy: {
'/api': {
target: 'http://localhost:3000',
changeOrigin: true
}
}
}
fetch('/api/users')