Skip to content

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:

  1. Verify Origins: Check if frontend URL matches CORS allowed origins
  2. Check Middleware Order: CORS middleware should be added first
  3. Verify Credentials: Set allow_credentials=True if using auth
  4. Test Preflight: Ensure OPTIONS requests are handled
  5. Check Headers: Verify all required headers are allowed
  6. Environment Check: Ensure correct origins for each environment
  7. Browser Cache: Clear browser cache and try incognito mode
  8. Network Tab: Check actual request/response headers in browser dev tools

External Resources