Main Ecosystems
HomePython CorePandas ReferenceNumPy ScientificFastAPI & PydanticDjango Enterprise
More Ecosystems
Environment & SetupRequests & HTTPAsyncIO ConcurrencyObject-Oriented OOPPyTorch Deep LearningScikit-Learn MLFlask FrameworkWeb ScrapingDatabase & ORMDevOps & Docker

FastAPI: Fixing CORS Policy Errors with React and Frontend Apps

Verified FixPython 3.10+FastAPI 0.110+, Starlette 0.36+Silo: fastapi

Quick Fix / Solution Rapide

This error occurs when Python tries to serve cross-origin HTTP requests without configuring CORSMiddleware. Add CORSMiddleware to your FastAPI application with explicit allowed origins, methods, and headers.

Root Cause Analysis

This error occurs when Python backend services running FastAPI fail to provide the required HTTP access-control headers in response to cross-origin requests made by browser-based frontend applications.

Root Cause 1: Browser Same-Origin Policy (SOP)

Modern web browsers enforce the Same-Origin Policy for security. When a React application running on http://localhost:3000 attempts to make an XMLHttpRequest or fetch call to a FastAPI backend on http://localhost:8000, the origin (scheme, host, and port) does not match. The browser blocks JavaScript from reading the response unless the backend explicitly includes the Access-Control-Allow-Origin header in its HTTP response.

Root Cause 2: Missing Preflight OPTIONS Handling

For non-simple HTTP requests (such as requests sending Content-Type: application/json, custom Authorization headers, or PUT/DELETE/PATCH methods), browsers send an automatic preflight OPTIONS request before sending the actual request. If FastAPI is not configured with CORSMiddleware, it returns a 405 Method Not Allowed or responds without CORS headers, causing the browser to abort the primary request.

Root Cause 3: Middleware Registration Order

In Starlette and FastAPI, middlewares are executed in reverse order of addition for incoming requests and standard order for outgoing responses. If custom middlewares intercept or short-circuit the request cycle before CORSMiddleware has evaluated the preflight request, the CORS headers will not be attached to error responses or redirect responses.

Root Cause 4: The Wildcard Origin with Credentials Pitfall

According to the W3C CORS specification, when a frontend application sends requests with credentials (credentials: 'include' or cookies / HTTP authentication), the backend must NOT return Access-Control-Allow-Origin: *. Combining allow_origins=["*"] with allow_credentials=True is explicitly rejected by modern browsers and will cause CORS errors.

Reproduction Code (MCVE)

Example: Bug Reproduction
from fastapi import FastAPI
from starlette.testclient import TestClient

app = FastAPI()

@app.get("/api/data")
def read_data():
    return {"status": "ok", "items": [1, 2, 3]}

# Test client simulating React app on http://localhost:3000 sending preflight OPTIONS
client = TestClient(app)
response = client.options(
    "/api/data",
    headers={
        "Origin": "http://localhost:3000",
        "Access-Control-Request-Method": "GET",
        "Access-Control-Request-Headers": "authorization,content-type"
    }
)

# In the absence of CORSMiddleware, FastAPI returns no Access-Control-Allow-Origin header
if "access-control-allow-origin" not in response.headers:
    raise AssertionError(
        "CORS policy error: Missing 'Access-Control-Allow-Origin' header in preflight response. "
        "React app at http://localhost:3000 was blocked by browser Same-Origin Policy."
    )

Solution 1: Add CORSMiddleware with Explicit Allowed Origins

Add CORSMiddleware to your FastAPI application, listing the explicit frontend domains, allowing credentials, and enabling all standard methods and headers.

Example: Recommended Solution
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from starlette.testclient import TestClient

app = FastAPI()

# Configure allowed origins (e.g. local React dev server and staging domain)
origins = [
    "http://localhost:3000",
    "http://127.0.0.1:3000",
    "https://myapp.example.com",
]

app.add_middleware(
    CORSMiddleware,
    allow_origins=origins,
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS", "PATCH"],
    allow_headers=["*"],
)

@app.get("/api/data")
def get_data():
    return {"status": "ok", "items": [1, 2, 3]}

# Test preflight OPTIONS request with configured CORS middleware
client = TestClient(app)
response = client.options(
    "/api/data",
    headers={
        "Origin": "http://localhost:3000",
        "Access-Control-Request-Method": "GET"
    }
)

print("Preflight Status:", response.status_code)
print("Allow-Origin Header:", response.headers.get("access-control-allow-origin"))
assert response.headers.get("access-control-allow-origin") == "http://localhost:3000" 

Solution 2: Dynamic Environment-Driven CORS Settings

Use environment variables to manage allowed CORS origins across development, staging, and production environments cleanly.

Example: Alternative Solution
import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Load origins from environment variable with fallback for local development
raw_origins = os.getenv("CORS_ORIGINS", "http://localhost:3000,http://localhost:5173")
allowed_origins = [origin.strip() for origin in raw_origins.split(",") if origin.strip()]

app.add_middleware(
    CORSMiddleware,
    allow_origins=allowed_origins,
    allow_credentials=True,
    allow_methods=["*"],
    allow_headers=["*"],
)

@app.get("/health")
def health_check():
    return {"status": "healthy", "cors_enabled_for": allowed_origins}

print("Configured CORS origins:", allowed_origins)

A widespread mistake is placing trailing slashes in origin URLs (e.g. "http://localhost:3000/" instead of "http://localhost:3000"). The HTTP Origin header sent by browsers never includes a trailing slash or path. Having a trailing slash in allow_origins will result in string comparison mismatch, causing silent CORS rejection.

Another common edge case occurs when custom authentication middleware returns a 401 Unauthorized or 403 Forbidden before CORSMiddleware processes the response. If your custom middleware does not pass through CORS headers on error responses, the browser will report a misleading CORS error instead of displaying the actual 401 status code.

Contrast CORS errors with HTTP 404 or 500 errors: A CORS error is strictly a browser-side security enforcement. The FastAPI server may have executed the endpoint logic successfully, but the browser discards the payload before Javascript can access it.