Laravel API: CORS ошибка - как исправить запрос, который блокируется браузером
Если вы делаете запрос с фронтенда на Laravel API и получаете в консоли что-то вроде Access to XMLHttpRequest has been blocked by CORS policy, эта статья - для вас. Разберём, почему так происходит, как быстро это исправить и как настроить CORS в Laravel правильно, без «костылей», которые потом ломаются в продакшене.
Подойдёт тем, кто использует Laravel как backend для SPA на Vue, React, Next.js или мобильного приложения, а фронтенд крутится на другом домене или порту.
Что такое CORS и почему он блокирует запрос
CORS (Cross-Origin Resource Sharing) - это механизм безопасности браузера, который запрещает странице с одного домена делать запросы к серверу на другом домене, если сервер явно не разрешил это через специальные HTTP-заголовки.
Важный момент: CORS - это ограничение браузера, а не Laravel. Сам API прекрасно обрабатывает запрос (это видно в логах Laravel и в Postman запрос проходит), но браузер блокирует ответ, потому что в нём нет нужных заголовков, например Access-Control-Allow-Origin.
Типичная ситуация: фронтенд работает на http://localhost:3000, а Laravel API
- на http://localhost:8000. Это разные origin (разные порты), поэтому браузер требует CORS-заголовки.
Почему возникает эта ошибка
Несколько типичных причин:
Не установлен пакет для CORS - в свежих версиях Laravel (начиная с 7) пакет fruitcake/laravel-cors встроен по умолчанию, но конфигурация может быть неполной или отсутствовать вовсе после миграции со старых версий.
Неправильно настроен config/cors.php - например, не указаны нужные пути (paths), origin или методы.
Preflight-запрос (OPTIONS) не обрабатывается - браузер перед основным запросом (POST, PUT, DELETE с заголовками вроде Content-Type: application/json) отправляет служебный запрос методом OPTIONS, и если сервер не отвечает на него корректно - основной запрос даже не отправляется.
Middleware CORS не подключён или подключён не в том порядке - например, после middleware, который требует авторизацию, из-за чего OPTIONS-запрос «упирается» в 401 раньше, чем дойдёт до CORS-логики.
Использование withCredentials: true на фронтенде при Access-Control-Allow-Origin: * - браузер запрещает комбинацию wildcard-origin с передачей cookie/credentials.
Частые ошибки и проблемы
«Я добавил заголовки вручную в контроллер, но всё равно не работает» - потому что preflight-запрос (OPTIONS) даже не доходит до контроллера, если он перехватывается другим middleware или не имеет маршрута.
«Работает в Postman, но не в браузере» - классика. Postman не проверяет CORS, поэтому это ложный сигнал «всё ок».
«Указал Access-Control-Allow-Origin: *, но куки не передаются» - wildcard несовместим с credentials.
«После настройки cors.php всё равно ошибка» - часто помогает забыть очистить конфиг-кэш командой
php artisan config:clear.«CORS работает локально, но не на проде» - обычно из-за того, что в supports_credentials или allowed_origins указан только локальный адрес.
Как исправить: пошаговое решение
Шаг 1. Проверьте, что пакет CORS установлен
В Laravel 7+ всё уже есть из коробки. Проверьте наличие файла:
config/cors.phpЕсли файла нет - опубликуйте конфигурацию:
php artisan vendor:publish --tag="cors"Если используете старую версию Laravel (5.x–6.x) и пакета нет вовсе, установите его:
composer require fruitcake/laravel-corsШаг 2. Настройте config/cors.php
Откройте файл и приведите его примерно к такому виду:
<?php
return [
'paths' => ['api/*', 'sanctum/csrf-cookie'],
'allowed_methods' => ['*'],
'allowed_origins' => ['http://localhost:3000', 'https://your-frontend.com'],
'allowed_origins_patterns' => [],
'allowed_headers' => ['*'],
'exposed_headers' => [],
'max_age' => 0,
'supports_credentials' => true,
];Ключевые моменты:
paths - обязательно включает все маршруты, которые вызывает фронтенд (часто забывают sanctum/csrf-cookie, если используется Laravel Sanctum).
allowed_origins - никогда не используйте *, если supports_credentials стоит в true. Указывайте конкретные домены.
supports_credentials - ставьте true, если фронтенд отправляет cookie (например, при аутентификации через Sanctum).
Шаг 3. Убедитесь, что middleware подключён
В Laravel 9+ middleware HandleCors уже глобально включён в app/Http/Kernel.php в группе $middleware. Проверьте:
protected $middleware = [
\Fruitcake\Cors\HandleCors::class,
// ...
];Если строки нет - добавьте её самой первой в списке, чтобы CORS-заголовки добавлялись до любых других проверок (например, авторизации).
В Laravel 11 структура немного изменилась - CORS настраивается через bootstrap/app.php, но конфигурационный файл config/cors.php работает так же.
Шаг 4. Очистите кэш конфигурации
После любых изменений в cors.php обязательно выполните:
php artisan config:clear
php artisan cache:clearИначе Laravel может продолжать использовать старую закэшированную конфигурацию, особенно на продакшен-сервере с php artisan config:cache.
Шаг 5. Проверьте preflight-запрос напрямую
Откройте DevTools → вкладка Network → найдите запрос с методом OPTIONS к вашему API-эндпоинту. В ответе должны быть заголовки:
Access-Control-Allow-Origin: http://localhost:3000
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, AuthorizationЕсли этого запроса нет вообще или он возвращает 404/401 - проблема в порядке middleware или в том, что маршрут защищён auth middleware, который блокирует OPTIONS до того, как CORS успевает отработать.
Шаг 6. Настройте фронтенд (если используете cookie-аутентификацию)
Если фронтенд использует axios с Sanctum:
axios.defaults.withCredentials = true;
axios.defaults.withXSRFToken = true;И убедитесь, что в .env Laravel указан правильный домен:
SANCTUM_STATEFUL_DOMAINS=localhost:3000
SESSION_DOMAIN=localhostЧасто задаваемые вопросы
Почему CORS работает в Postman, но не в браузере? Postman не реализует политику CORS - это исключительно браузерное ограничение. Поэтому успешный ответ в Postman ничего не говорит о том, настроен ли CORS правильно.
Можно ли просто отключить CORS в браузере для теста? Технически да (флаги Chrome или расширения), но это решение только для локальной отладки. На проде у пользователей не будет таких расширений, и API останется недоступным.
Что делать, если ошибка появляется только на конкретном маршруте? Проверьте, входит ли этот маршрут в paths в config/cors.php. Часто забывают добавить вложенные пути или маршруты вне группы api/*.
Почему Access-Control-Allow-Origin: * не подходит для авторизованных запросов? Спецификация CORS прямо запрещает комбинацию wildcard-origin с Access-Control-Allow-Credentials: true - браузер просто заблокирует такой ответ.
Нужно ли что-то настраивать в Nginx/Apache отдельно? Обычно нет - CORS-заголовки добавляет Laravel через middleware. Но если перед Laravel стоит reverse-proxy, который сам обрезает или дублирует заголовки, могут возникать конфликты - стоит проверить конфиг proxy_pass.
Ошибка пропадает после php artisan config:clear, но возвращается через время - почему? Скорее всего, в деплой-скрипте после очистки кэша снова вызывается php artisan config:cache со старой версией .env или конфига. Проверьте порядок команд в деплое.
Как отличить CORS-ошибку от обычной 500/401 ошибки? В консоли браузера CORS-ошибка явно содержит текст «CORS policy» или «No 'Access-Control-Allow-Origin' header». Если в Network виден код 500 или 401 с нормальными заголовками - проблема не в CORS, а в самой логике или авторизации.
Нужно ли настраивать CORS для запросов с одного и того же домена? Нет. CORS требуется только для cross-origin запросов - то есть когда отличается домен, порт или протокол (http/https).
Полезные советы и лучшие практики
Никогда не используйте allowed_origins => ['*'] в продакшене, если есть аутентификация через cookie - указывайте конкретные домены явно.
Добавляйте все используемые фронтендом эндпоинты в paths, включая sanctum/csrf-cookie и broadcasting/auth, если используете Laravel Echo.
При деплое через CI/CD добавляйте php artisan config:clear перед config:cache, чтобы избежать использования устаревших настроек.
Для разработки удобно держать отдельный .env.local с локальными origin, а в проде - рабочий список доменов.
Используйте переменные окружения для allowed_origins, чтобы не хардкодить домены и легко переключаться между окружениями.
Итог
CORS-ошибка в Laravel API почти всегда означает, что браузер не получил нужные заголовки в ответе на preflight-запрос. Решение - правильно настроить config/cors.php, убедиться, что middleware HandleCors подключён первым, очистить кэш конфигурации и проверить заголовки в DevTools. После этих шагов запрос с фронтенда начнёт проходить без блокировок.
Комментарии
Чтобы оставить комментарий, войдите в аккаунт.