Skip to main content

FastAPI CORS Implementation

Introduction

When building APIs with FastAPI, you'll likely encounter situations where your API needs to be accessed from web applications hosted on different domains. This is where Cross-Origin Resource Sharing (CORS) comes into play.

CORS is a security feature implemented by browsers that restricts web pages from making requests to a different domain than the one that served the original page. This is a critical security measure that prevents malicious websites from making unauthorized requests to your API on behalf of your users.

In this tutorial, we'll explore:

  • What CORS is and why it's important
  • How to implement CORS in FastAPI
  • Common CORS configurations and best practices
  • Troubleshooting CORS issues

Understanding CORS

What is CORS?

CORS stands for Cross-Origin Resource Sharing. It's a mechanism that allows resources on a web page to be requested from another domain outside the domain from which the original resource was served.

When a web application makes a request to a different origin (domain, protocol, or port), the browser sends a preflight request to determine if the actual request is safe to send. This preflight request uses the HTTP OPTIONS method and includes headers that describe the actual request that will follow.

When Do You Need CORS?

You need to implement CORS when:

  1. Your frontend (like a React, Angular, or Vue app) is hosted on a different domain than your API
  2. You're developing locally and your frontend runs on localhost:3000 while your API runs on localhost:8000
  3. Your API needs to be accessible by third-party applications

Implementing CORS in FastAPI

FastAPI provides built-in support for CORS through the CORSMiddleware. Let's implement it step by step:

Basic CORS Implementation

python
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# Add CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"], # List of allowed origins
allow_credentials=True, # Allow cookies to be included in requests
allow_methods=["*"], # Allow all methods
allow_headers=["*"], # Allow all headers
)

@app.get("/")
async def root():
return {"message": "Hello World"}

This basic implementation allows requests from http://localhost:3000 to access your API.

CORS Parameters Explained

Let's go through each parameter in more detail:

  1. allow_origins: List of origins that are allowed to make requests to your API

    python
    # Example: Allow specific origins
    allow_origins=["http://localhost:3000", "https://myapp.com"]

    # Allow all origins (not recommended for production)
    allow_origins=["*"]
  2. allow_credentials: Whether to support credentials like cookies, authorization headers, or TLS client certificates

    python
    # Enable credentials support
    allow_credentials=True
  3. allow_methods: HTTP methods that can be used when making the actual request

    python
    # Allow specific methods
    allow_methods=["GET", "POST", "PUT", "DELETE"]

    # Allow all methods
    allow_methods=["*"]
  4. allow_headers: HTTP request headers that can be used when making the actual request

    python
    # Allow specific headers
    allow_headers=["Authorization", "Content-Type"]

    # Allow all headers
    allow_headers=["*"]
  5. expose_headers: Headers that browsers are allowed to access from the response

    python
    expose_headers=["X-Custom-Header"]
  6. max_age: Maximum time (in seconds) the results of a preflight request can be cached

    python
    max_age=600  # Cache for 10 minutes

Common CORS Configurations

Development Environment

During development, you might want to allow all origins temporarily:

python
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Allow all origins in development
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

Production Environment

For production, it's best to specify exactly which origins are allowed:

python
origins = [
"https://frontend.myapp.com",
"https://app.myapp.com",
"https://myapp.com",
]

app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)

API Service with Multiple Clients

If your API serves multiple clients, you might need a more flexible approach:

python
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware

app = FastAPI()

# List of allowed origins
allowed_origins = [
"https://client1.example.com",
"https://client2.example.com",
"https://client3.example.com",
]

# Function to get origins from environment variables
def get_allowed_origins():
import os
# Get origins from environment variable, fallback to predefined list
origins_str = os.getenv("ALLOWED_ORIGINS", "")
if origins_str:
return origins_str.split(",")
return allowed_origins

app.add_middleware(
CORSMiddleware,
allow_origins=get_allowed_origins(),
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Authorization", "Content-Type"],
expose_headers=["X-Custom-Header", "Content-Length"],
max_age=600,
)

Real-World Example: Creating a Public API with CORS

Let's create a simple weather API that can be accessed from various clients:

python
from fastapi import FastAPI, HTTPException
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from typing import Dict, Optional
import random

app = FastAPI(title="Weather API")

# CORS configuration
app.add_middleware(
CORSMiddleware,
allow_origins=[
"http://localhost:3000", # React development server
"https://weather-app.example.com", # Production frontend
"https://partner-app.example.com", # Partner application
],
allow_credentials=True,
allow_methods=["GET"],
allow_headers=["Content-Type", "X-API-Key"],
)

# Mock database of weather data
weather_data: Dict[str, Dict] = {
"new-york": {
"temperature": 22,
"humidity": 65,
"condition": "Partly Cloudy"
},
"london": {
"temperature": 18,
"humidity": 75,
"condition": "Rainy"
},
"tokyo": {
"temperature": 28,
"humidity": 60,
"condition": "Sunny"
}
}

class WeatherResponse(BaseModel):
city: str
temperature: int
humidity: int
condition: str

@app.get("/api/weather/{city}", response_model=WeatherResponse)
async def get_weather(city: str):
city = city.lower()
if city not in weather_data:
raise HTTPException(status_code=404, detail="City not found")

return {
"city": city.title(),
**weather_data[city]
}

@app.get("/api/weather/random")
async def get_random_weather():
city = random.choice(list(weather_data.keys()))
return {
"city": city.title(),
**weather_data[city]
}

In this example:

  1. We've configured CORS to allow requests from specific origins
  2. We're only allowing GET requests since this API is read-only
  3. We're permitting the Content-Type and X-API-Key headers
  4. The API can be accessed from the specified origins without CORS errors

Troubleshooting CORS Issues

Common CORS Errors

  1. "Access to XMLHttpRequest at 'X' from origin 'Y' has been blocked by CORS policy"

    • Check that the origin making the request is included in your allow_origins list
    • Verify that the protocol (http vs https) matches exactly
  2. "Request header field 'X' is not allowed by Access-Control-Allow-Headers"

    • Add the required header to your allow_headers list
  3. "Method 'X' is not allowed by Access-Control-Allow-Methods"

    • Add the required method to your allow_methods list

Debugging CORS

To debug CORS issues:

  1. Check the browser's developer console for detailed error messages
  2. Use tools like Postman to inspect the preflight responses
  3. Temporarily set all CORS options to their most permissive settings to isolate the issue
  4. Add logging to your FastAPI application:
python
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger("fastapi")

@app.middleware("http")
async def log_requests(request, call_next):
logger.info(f"Request: {request.method} {request.url}")
logger.info(f"Headers: {request.headers}")
response = await call_next(request)
logger.info(f"Response status: {response.status_code}")
return response

Security Considerations

While implementing CORS, keep these security considerations in mind:

  1. Never use wildcard origins in production

    • allow_origins=["*"] should only be used for development or public APIs
    • For APIs that handle sensitive data, always specify exact origins
  2. Be careful with credentials

    • When allow_credentials=True, you can't use wildcard origins
    • This helps prevent cross-site request forgery (CSRF) attacks
  3. Minimize exposed headers

    • Only expose headers that client applications actually need
  4. Consider using a CORS origin allowlist

    • Store allowed origins in a database or configuration file
    • Validate origins against this allowlist

Summary

In this tutorial, you've learned:

  • What CORS is and why it's important for web API security
  • How to implement CORS in FastAPI applications
  • How to configure various CORS parameters for different scenarios
  • Common CORS issues and how to troubleshoot them
  • Security best practices when working with CORS

CORS is a critical security feature that allows you to control which domains can access your API. By properly implementing CORS in FastAPI, you can build secure APIs that can be safely consumed by web applications across different domains.

Additional Resources

Exercises

  1. Set up a FastAPI application with CORS that allows requests only from "https://myapp.com" and "https://api.myapp.com".

  2. Create an API endpoint that requires a custom header X-Client-ID and configure CORS to allow this header.

  3. Implement a dynamic CORS configuration that reads allowed origins from a JSON file.

  4. Create a FastAPI application with different CORS settings for different route groups or prefixes.

  5. Implement logging middleware to debug CORS preflight requests and responses.



If you spot any mistakes on this website, please let me know at feedback@compilenrun.com. I’d greatly appreciate your feedback! :)