FastAPI CORS with React - Complete Setup Guide
CORS (Cross-Origin Resource Sharing) errors are the most common issue when connecting React frontends to FastAPI backends. This guide provides complete solutions for all CORS scenarios in development and production.
Understanding CORS in FastAPI + React
CORS errors occur when your React app (running on one port) tries to access your FastAPI backend (running on another port). Browsers block these requests by default for security reasons.
Common CORS Error Messages
Access to fetch at 'http://localhost:8000/api/users' from origin 'http://localhost:3000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.
Access to fetch at 'http://localhost:8000/api/login' from origin 'http://localhost:3000' has been blocked by CORS policy: Request header field content-type is not allowed by Access-Control-Allow-Headers in preflight response.
Basic CORS Setup
1. Install FastAPI CORS Middleware
# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Basic CORS setup
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # React dev server
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
@app.get("/api/users")
async def get_users():
return {"users": []}
2. Environment-Based CORS Configuration
# config.py
import os
from typing import List
class Settings:
# Development origins
CORS_ORIGINS_DEV: List[str] = [
"http://localhost:3000", # React dev server
"http://127.0.0.1:3000",
"http://localhost:3001", # Alternative React port
]
# Production origins
CORS_ORIGINS_PROD: List[str] = [
"https://yourdomain.com",
"https://www.yourdomain.com",
"https://app.yourdomain.com",
]
@property
def CORS_ORIGINS(self) -> List[str]:
if os.getenv("ENVIRONMENT") == "production":
return self.CORS_ORIGINS_PROD
return self.CORS_ORIGINS_DEV
settings = Settings()
# main.py
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
from config import settings
app = FastAPI()
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["*"],
)
Advanced CORS Configurations
1. Handling Authentication with CORS
# When using cookies or authorization headers
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True, # Required for cookies/auth headers
allow_methods=["*"],
allow_headers=["*"],
)
# In your React app, include credentials
fetch('http://localhost:8000/api/protected', {
method: 'GET',
credentials: 'include', # Include cookies
headers: {
'Authorization': 'Bearer your-token',
'Content-Type': 'application/json',
},
});
2. Specific Headers Configuration
# Allow specific headers only
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=[
"accept",
"accept-encoding",
"authorization",
"content-type",
"dnt",
"origin",
"user-agent",
"x-csrftoken",
"x-requested-with",
],
)
3. Multiple Environment Setup
# main.py
import os
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
# Get environment
ENVIRONMENT = os.getenv("ENVIRONMENT", "development")
if ENVIRONMENT == "development":
# Permissive CORS for development
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allow all origins in dev
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
elif ENVIRONMENT == "staging":
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://staging.yourdomain.com",
"https://preview.yourdomain.com",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)
else: # production
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://yourdomain.com",
"https://www.yourdomain.com",
],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["accept", "authorization", "content-type"],
)
React Frontend Configuration
1. Axios Configuration
// api/client.ts
import axios from 'axios';
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
const apiClient = axios.create({
baseURL: API_BASE_URL,
withCredentials: true, // Include cookies
headers: {
'Content-Type': 'application/json',
},
});
// Add request interceptor for auth token
apiClient.interceptors.request.use(
(config) => {
const token = localStorage.getItem('access_token');
if (token) {
config.headers.Authorization = `Bearer ${token}`;
}
return config;
},
(error) => Promise.reject(error)
);
export default apiClient;
2. Fetch API Configuration
// utils/api.ts
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
async function apiRequest(endpoint: string, options: RequestInit = {}) {
const url = `${API_BASE_URL}${endpoint}`;
const config: RequestInit = {
credentials: 'include', // Include cookies
headers: {
'Content-Type': 'application/json',
...options.headers,
},
...options,
};
// Add auth token if available
const token = localStorage.getItem('access_token');
if (token) {
config.headers = {
...config.headers,
'Authorization': `Bearer ${token}`,
};
}
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return response.json();
}
export { apiRequest };
3. Environment Variables
# .env.development
REACT_APP_API_URL=http://localhost:8000
# .env.production
REACT_APP_API_URL=https://api.yourdomain.com
Common CORS Issues and Solutions
1. Preflight Request Failures
Problem: OPTIONS requests failing before actual request
# Solution: Explicitly handle OPTIONS method
from fastapi import FastAPI, Request, Response
app = FastAPI()
@app.options("/{path:path}")
async def options_handler(request: Request, response: Response):
response.headers["Access-Control-Allow-Origin"] = "http://localhost:3000"
response.headers["Access-Control-Allow-Methods"] = "GET, POST, PUT, DELETE, OPTIONS"
response.headers["Access-Control-Allow-Headers"] = "*"
response.headers["Access-Control-Allow-Credentials"] = "true"
return {}
# Add CORS middleware as well
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
2. Middleware Order Issues
# WRONG - CORS middleware after other middleware
app.add_middleware(SomeOtherMiddleware)
app.add_middleware(CORSMiddleware, ...) # Too late
# CORRECT - CORS middleware first
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
app.add_middleware(SomeOtherMiddleware)
3. Router-Level CORS Issues
# Problem: Router routes not inheriting CORS
from fastapi import APIRouter
router = APIRouter(prefix="/api")
@router.get("/users")
async def get_users():
return {"users": []}
# Solution: Include router before CORS setup
app.include_router(router)
# Then add CORS middleware
app.add_middleware(CORSMiddleware, ...)
4. Development vs Production Origins
# Dynamic origin handling
import re
def is_cors_allowed(origin: str) -> bool:
# Allow localhost with any port in development
localhost_pattern = r"^https?://localhost:\d+$"
if re.match(localhost_pattern, origin):
return True
# Allow specific production domains
allowed_domains = [
"https://yourdomain.com",
"https://www.yourdomain.com",
]
return origin in allowed_domains
app.add_middleware(
CORSMiddleware,
allow_origin_regex=r"^https?://localhost:\d+$", # Development
allow_origins=["https://yourdomain.com"], # Production
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Docker Development Setup
1. Docker Compose CORS Configuration
# docker-compose.yml
version: '3.8'
services:
backend:
build: .
ports:
- "8000:8000"
environment:
- CORS_ORIGINS=http://localhost:3000,http://host.docker.internal:3000
frontend:
image: node:16
working_dir: /app
volumes:
- ./frontend:/app
ports:
- "3000:3000"
command: npm start
environment:
- REACT_APP_API_URL=http://localhost:8000
2. Docker Network CORS
# When frontend and backend in same Docker network
import os
# Allow Docker internal communication
docker_origins = [
"http://frontend:3000", # Docker service name
"http://localhost:3000", # Host access
]
if os.getenv("DOCKER_ENV"):
cors_origins.extend(docker_origins)
Testing CORS Configuration
1. Manual CORS Testing
# Test preflight request
curl -X OPTIONS http://localhost:8000/api/users \
-H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: Content-Type" \
-v
# Test actual request
curl -X GET http://localhost:8000/api/users \
-H "Origin: http://localhost:3000" \
-v
2. Automated CORS Testing
# test_cors.py
from fastapi.testclient import TestClient
from main import app
client = TestClient(app)
def test_cors_preflight():
response = client.options(
"/api/users",
headers={
"Origin": "http://localhost:3000",
"Access-Control-Request-Method": "POST",
"Access-Control-Request-Headers": "Content-Type",
}
)
assert response.status_code == 200
assert "access-control-allow-origin" in response.headers
assert response.headers["access-control-allow-origin"] == "http://localhost:3000"
def test_cors_actual_request():
response = client.get(
"/api/users",
headers={"Origin": "http://localhost:3000"}
)
assert response.status_code == 200
assert "access-control-allow-origin" in response.headers
Production Deployment Considerations
1. Reverse Proxy CORS
# nginx.conf - when using nginx
server {
listen 80;
server_name yourdomain.com;
location /api/ {
proxy_pass http://backend:8000/;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# Handle CORS at proxy level (optional)
add_header 'Access-Control-Allow-Origin' 'https://yourdomain.com' always;
add_header 'Access-Control-Allow-Credentials' 'true' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, PUT, DELETE, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'Accept,Authorization,Cache-Control,Content-Type,DNT,If-Modified-Since,Keep-Alive,Origin,User-Agent,X-Requested-With' always;
if ($request_method = 'OPTIONS') {
return 204;
}
}
}
2. CDN and CORS
# When using CDN, add CDN domains to CORS
cdn_origins = [
"https://cdn.yourdomain.com",
"https://d1234567890.cloudfront.net",
]
app.add_middleware(
CORSMiddleware,
allow_origins=[
"https://yourdomain.com",
*cdn_origins,
],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
Debugging CORS Issues
1. Browser Developer Tools
// Check CORS headers in browser console
fetch('http://localhost:8000/api/users')
.then(response => {
console.log('CORS headers:');
console.log('Access-Control-Allow-Origin:', response.headers.get('Access-Control-Allow-Origin'));
console.log('Access-Control-Allow-Credentials:', response.headers.get('Access-Control-Allow-Credentials'));
return response.json();
})
.catch(error => console.error('CORS error:', error));
2. FastAPI CORS Logging
import logging
from fastapi import FastAPI, Request
app = FastAPI()
@app.middleware("http")
async def log_cors(request: Request, call_next):
logger = logging.getLogger("cors")
origin = request.headers.get("origin")
method = request.method
logger.info(f"CORS Request: {method} from {origin}")
response = await call_next(request)
logger.info(f"CORS Response headers: {dict(response.headers)}")
return response
Troubleshooting Checklist
When facing CORS issues:
- Verify Origins: Check if frontend URL matches CORS allowed origins
- Check Middleware Order: CORS middleware should be added first
- Verify Credentials: Set
allow_credentials=True
if using auth - Test Preflight: Ensure OPTIONS requests are handled
- Check Headers: Verify all required headers are allowed
- Environment Check: Ensure correct origins for each environment
- Browser Cache: Clear browser cache and try incognito mode
- Network Tab: Check actual request/response headers in browser dev tools