CORS в FastAPI: почему GET работает в curl, а браузер блокирует POST
curl успешно создаёт заказ, а frontend на localhost:3000 получает ошибку CORS. Сам endpoint может быть исправен. Браузер дополнительно проверяет, разрешено ли странице с одного origin читать ответ сервера на другом. Для некоторых запросов перед POST выполняется предварительный OPTIONS.
Origin включает схему, имя и порт
http://localhost:3000 и http://localhost:8000 — разные origin. Разные протоколы тоже меняют origin. В allow_origins укажите адрес frontend, который действительно виден браузеру. Путь /dashboard туда не входит: origin заканчивается после порта.
Preflight сообщает предполагаемый метод и заголовки через Access-Control-Request-Method и Access-Control-Request-Headers. CORSMiddleware отвечает на него до обычного маршрута. Поэтому создание отдельного OPTIONS-обработчика для каждого endpoint обычно не требуется. Разрешите только используемые методы и заголовки, а при credentials задайте явные значения вместо бездумного wildcard.
Проверим разрешённый и чужой origin
Установите FastAPI и HTTPX. Тест не превращает TestClient в браузер: он проверяет HTTP-заголовки middleware, по которым браузер принимает решение. Сам клиент теста не запрещает читать JSON при отсутствии CORS-разрешения.
Сохраните весь блок в файл и запустите Python 3.11+ в отдельном окружении с пакетами fastapi, httpx. Для моделей используется Pydantic v2; отдельный сервер для TestClient не нужен.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from fastapi.testclient import TestClient
app = FastAPI()
app.add_middleware(CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["GET", "POST"],
allow_headers=["Authorization", "Content-Type"])
@app.post("/orders")
def order():
return {"created": True}
with TestClient(app) as client:
headers = {"Origin": "http://localhost:3000",
"Access-Control-Request-Method": "POST",
"Access-Control-Request-Headers": "authorization,content-type"}
preflight = client.options("/orders", headers=headers)
assert preflight.status_code == 200
assert preflight.headers["access-control-allow-origin"] == headers["Origin"]
assert "POST" in preflight.headers["access-control-allow-methods"]
denied = client.options("/orders", headers={**headers, "Origin": "http://other.test"})
assert denied.status_code == 400
assert "access-control-allow-origin" not in denied.headers
actual = client.post("/orders", headers={"Origin": headers["Origin"]})
assert actual.json() == {"created": True}
assert actual.headers["access-control-allow-origin"] == headers["Origin"]
print("allowed preflight, denied origin and actual response checked")CORS не заменяет авторизацию
Сервер по-прежнему может получить запрос от curl или другого backend независимо от настроек CORS. Даже браузер способен отправить некоторые запросы, ответ на которые не позволит прочитать скрипту. Поэтому секретные операции требуют проверки пользователя и прав; CORS управляет чтением ответов страницей, а не списком всех допустимых клиентов.
Если используете cookie, согласуйте allow_credentials, fetch credentials и параметры самой cookie. При работе с Bearer нужен разрешённый Authorization. Не смешивайте эти сценарии в случайный набор флагов: сначала посмотрите реальный запрос во вкладке Network и определите, какой заголовок или origin отклонён.
Как диагностировать ошибку на сервере
Если OPTIONS возвращает 404 или 405, убедитесь, что middleware подключено к тому приложению, которое обслуживает адрес, и что прокси пропускает запрос. Если preflight успешен, смотрите заголовки фактического POST: ошибка сервера тоже должна пройти нужную цепочку middleware. Простой успех GET не подтверждает договор POST с Authorization.
После исправления сохраните тест с точным origin, методом и набором заголовков frontend. Добавьте отрицательный пример чужого origin: он обнаружит случайное расширение разрешений до всех сайтов. При публикации замените localhost на реальный адрес интерфейса и проверьте схему https.
Следующая практика
В открытом уроке первого проекта FastAPI вы запустите API и проверите маршрут. После этого подключите небольшой frontend с другого порта и посмотрите в Network, как к рабочему запросу добавляется браузерная проверка CORS. Откройте бесплатный урок запуска FastAPI.
В бесплатном курсе FastAPI первый запуск развивается в backend с базой, входом и тестами. Когда появится защищённый запрос frontend, проверьте его preflight вместе с поведением самого endpoint. Откройте программу курса.
Продолжить на практике
- FastAPI для начинающих: API с базой данных и тестами ↗
Соберёте свой первый полноценный backend на FastAPI: с базой данных, регистрацией, JWT-авторизацией и автотестами. 34 коротких урока и 140 заданий с проверкой — бесплатно.
