Laravel API: CORS ошибка - как исправить запрос, который блокируется браузером

mr. Cooper 14 часов назад Веб-разработка
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-заголовки.

Почему возникает эта ошибка

Несколько типичных причин:

  1. Не установлен пакет для CORS - в свежих версиях Laravel (начиная с 7) пакет fruitcake/laravel-cors встроен по умолчанию, но конфигурация может быть неполной или отсутствовать вовсе после миграции со старых версий.

  2. Неправильно настроен config/cors.php - например, не указаны нужные пути (paths), origin или методы.

  3. Preflight-запрос (OPTIONS) не обрабатывается - браузер перед основным запросом (POST, PUT, DELETE с заголовками вроде Content-Type: application/json) отправляет служебный запрос методом OPTIONS, и если сервер не отвечает на него корректно - основной запрос даже не отправляется.

  4. Middleware CORS не подключён или подключён не в том порядке - например, после middleware, который требует авторизацию, из-за чего OPTIONS-запрос «упирается» в 401 раньше, чем дойдёт до CORS-логики.

  5. Использование 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

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

  1. Почему CORS работает в Postman, но не в браузере? Postman не реализует политику CORS - это исключительно браузерное ограничение. Поэтому успешный ответ в Postman ничего не говорит о том, настроен ли CORS правильно.

  2. Можно ли просто отключить CORS в браузере для теста? Технически да (флаги Chrome или расширения), но это решение только для локальной отладки. На проде у пользователей не будет таких расширений, и API останется недоступным.

  3. Что делать, если ошибка появляется только на конкретном маршруте? Проверьте, входит ли этот маршрут в paths в config/cors.php. Часто забывают добавить вложенные пути или маршруты вне группы api/*.

  4. Почему Access-Control-Allow-Origin: * не подходит для авторизованных запросов? Спецификация CORS прямо запрещает комбинацию wildcard-origin с Access-Control-Allow-Credentials: true - браузер просто заблокирует такой ответ.

  5. Нужно ли что-то настраивать в Nginx/Apache отдельно? Обычно нет - CORS-заголовки добавляет Laravel через middleware. Но если перед Laravel стоит reverse-proxy, который сам обрезает или дублирует заголовки, могут возникать конфликты - стоит проверить конфиг proxy_pass.

  6. Ошибка пропадает после php artisan config:clear, но возвращается через время - почему? Скорее всего, в деплой-скрипте после очистки кэша снова вызывается php artisan config:cache со старой версией .env или конфига. Проверьте порядок команд в деплое.

  7. Как отличить CORS-ошибку от обычной 500/401 ошибки? В консоли браузера CORS-ошибка явно содержит текст «CORS policy» или «No 'Access-Control-Allow-Origin' header». Если в Network виден код 500 или 401 с нормальными заголовками - проблема не в CORS, а в самой логике или авторизации.

  8. Нужно ли настраивать 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. После этих шагов запрос с фронтенда начнёт проходить без блокировок.

Комментарии

Пока нет комментариев. Будьте первым, кто напишет.

Чтобы оставить комментарий, войдите в аккаунт.

Похожие статьи