CORS preflight — OPTIONS-запрос, который browser отправляет перед не-simple request (custom headers, non-GET). Если сервер не отвечает 200/204 с правильными CORS-headers, actual request не идёт. Частые fixes: ответить на OPTIONS в nginx, добавить Access-Control-Allow-Headers с вашими custom headers, установить Access-Control-Max-Age.
Ниже: пошаговая инструкция, рабочие примеры, типичные ошибки, FAQ.
if ($request_method = OPTIONS) блокAccess-Control-Max-Age: 86400app.options('*', cors()) для handle global preflight| Сценарий | Конфиг |
|---|---|
| nginx OPTIONS handler | location /api/ {
if ($request_method = OPTIONS) {
add_header 'Access-Control-Allow-Origin' '$http_origin';
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE';
add_header 'Access-Control-Allow-Headers' 'Authorization, Content-Type, X-Request-Id';
add_header 'Access-Control-Max-Age' 86400;
return 204;
}
# ... actual proxy_pass
} |
| Express.js | app.use(cors({
origin: 'https://app.example.com',
methods: ['GET','POST','PUT','DELETE'],
allowedHeaders: ['Authorization','Content-Type','X-Request-Id'],
credentials: true,
maxAge: 86400
})); |
| Django settings.py | CORS_ALLOWED_ORIGINS = ['https://app.example.com']
CORS_ALLOW_HEADERS = ['authorization','content-type','x-request-id']
CORS_ALLOW_CREDENTIALS = True |
| Simple vs preflight triggers | Simple: GET + только Accept+Content-Type (в whitelist)
Preflight: PUT, PATCH, DELETE, или any custom header |
| Preflight caching (reduce load) | Access-Control-Max-Age: 86400 # browser cache preflight 24h |
Non-simple request. Simple: GET/HEAD/POST + только standard headers. Любая custom header, PUT/DELETE/PATCH, Content-Type не в whitelist → preflight.
Access-Control-Max-Age: 86400 кэширует preflight 24 часа. Browser не повторяет OPTIONS на каждый request.
Да. JWT в Authorization header → custom header → preflight обязателен. DRF-cors settings должен содержать "authorization".
<a href="/cors">Enterno CORS checker</a> → URL + Origin → увидите preflight ответ + headers.