Skip to main content

FastAPI CORS Middleware

Introduction

When building web applications with FastAPI, you'll likely encounter situations where your API needs to communicate with frontend applications hosted on different domains or origins. By default, web browsers implement a security feature called the Same-Origin Policy, which restricts how a document or script loaded from one origin can interact with resources from another origin.

Cross-Origin Resource Sharing (CORS) is a mechanism that allows servers to specify who can access their resources and how. FastAPI provides built-in support for CORS through its CORSMiddleware, making it easy to configure these permissions in your application.

In this tutorial, we'll learn:

  • What CORS is and why it's important
  • How to implement the CORS middleware in FastAPI
  • Various configuration options for CORS
  • Testing CORS settings in real-world scenarios

Understanding CORS

Before we dive into implementation, let's understand what CORS is and why it's necessary.

What is CORS?

CORS stands for Cross-Origin Resource Sharing. It's a security feature implemented by browsers to prevent potentially malicious websites from making requests to APIs on different domains without permission.

An "origin" consists of:

  • Protocol (e.g., http, https)
  • Host (e.g., example.com)
  • Port (e.g., 80, 443)

If any of these differ between the website making the request and the API receiving it, it's considered a cross-origin request.

Why is CORS Important?

Without CORS, a malicious website could make unauthorized requests to an API using the credentials of logged-in users, leading to potential security breaches. CORS gives API developers control over which origins can access their resources.

Implementing CORS Middleware in FastAPI

FastAPI's CORSMiddleware makes it simple to add CORS headers to your responses. Here's how to implement it:

Basic 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", "https://myapp.com"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

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

In this basic example, we're allowing requests from two origins: http://localhost:3000 (typically a local development server) and https://myapp.com (a production server).

CORS Configuration Options

Let's explore the different parameters you can set in the CORS middleware:

allow_origins

This parameter specifies which origins are allowed to make requests to your API.

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

# Allow all origins (less secure, but sometimes needed for development)
allow_origins=["*"]

allow_origin_regex

If you need more flexibility, you can use a regex pattern to match origins:

python
# Allow all subdomains of myapp.com
allow_origin_regex="https://.*\.myapp\.com"

allow_methods

Specifies which HTTP methods are allowed:

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

# Allow all methods
allow_methods=["*"]

allow_headers

Controls which HTTP headers can be used during the request:

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

# Allow all headers
allow_headers=["*"]

allow_credentials

Indicates whether the response can include credentials (cookies, authorization headers):

python
# Allow credentials
allow_credentials=True

# Don't allow credentials
allow_credentials=False

expose_headers

Specifies which headers should be exposed to the browser:

python
expose_headers=["Content-Length", "X-Custom-Header"]

max_age

Defines how long the results of a preflight request can be cached (in seconds):

python
max_age=600  # Cache for 10 minutes

Complete Example with All Options

Here's a more comprehensive example showing all available options:

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

app = FastAPI()

app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000", "https://myapp.com"],
allow_origin_regex="https://.*\.myapp\.com",
allow_methods=["GET", "POST", "PUT", "DELETE", "OPTIONS"],
allow_headers=["Content-Type", "Authorization", "X-Custom-Header"],
allow_credentials=True,
expose_headers=["Content-Length", "X-Custom-Header"],
max_age=600,
)

@app.get("/api/users")
async def get_users():
return [{"id": 1, "name": "John"}, {"id": 2, "name": "Jane"}]

@app.post("/api/users")
async def create_user(user: dict):
return {"message": "User created successfully", "user": user}

Understanding How CORS Works Behind the Scenes

When a browser makes a cross-origin request, the following happens:

  1. For simple requests (GET, POST with certain content types), the browser adds an Origin header to the request.

  2. For complex requests (PUT, DELETE, or requests with custom headers), the browser first sends a "preflight" OPTIONS request to check if the actual request is permitted.

  3. The server responds with appropriate CORS headers, and the browser either allows or blocks the actual request based on these headers.

Here's what a preflight request and response might look like:

Preflight Request:

OPTIONS /api/users HTTP/1.1
Host: api.myapp.com
Origin: https://myapp.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type, Authorization

Preflight Response:

HTTP/1.1 200 OK
Access-Control-Allow-Origin: https://myapp.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Content-Type, Authorization
Access-Control-Max-Age: 600

Real-world Application: Building a Frontend and API

Let's see how CORS works in a real-world scenario where we have a React frontend and a FastAPI backend on different origins.

FastAPI Backend (Running on http://localhost:8000)

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

app = FastAPI()

app.add_middleware(
CORSMiddleware,
allow_origins=["http://localhost:3000"],
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)

class Item(BaseModel):
name: str
description: str = None

items = []

@app.get("/api/items")
async def get_items():
return items

@app.post("/api/items")
async def create_item(item: Item):
items.append(item)
return {"message": "Item created successfully", "item": item}

React Frontend (Running on http://localhost:3000)

javascript
import React, { useState, useEffect } from 'react';

function App() {
const [items, setItems] = useState([]);
const [name, setName] = useState('');
const [description, setDescription] = useState('');

useEffect(() => {
// Fetch items when component mounts
fetchItems();
}, []);

const fetchItems = async () => {
try {
const response = await fetch('http://localhost:8000/api/items');
const data = await response.json();
setItems(data);
} catch (error) {
console.error('Error fetching items:', error);
}
};

const handleSubmit = async (e) => {
e.preventDefault();
try {
const response = await fetch('http://localhost:8000/api/items', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({ name, description }),
});

if (response.ok) {
// Clear form and refresh items
setName('');
setDescription('');
fetchItems();
}
} catch (error) {
console.error('Error creating item:', error);
}
};

return (
<div className="App">
<h1>Items Manager</h1>

<form onSubmit={handleSubmit}>
<div>
<label>Name:</label>
<input
type="text"
value={name}
onChange={(e) => setName(e.target.value)}
required
/>
</div>
<div>
<label>Description:</label>
<input
type="text"
value={description}
onChange={(e) => setDescription(e.target.value)}
/>
</div>
<button type="submit">Add Item</button>
</form>

<h2>Items List:</h2>
<ul>
{items.map((item, index) => (
<li key={index}>
<strong>{item.name}</strong>: {item.description || 'No description'}
</li>
))}
</ul>
</div>
);
}

export default App;

In this example:

  1. The FastAPI backend runs on port 8000
  2. The React frontend runs on port 3000
  3. The CORS middleware allows requests from the frontend origin
  4. The frontend makes both GET and POST requests to the backend

Common CORS Issues and Solutions

Issue 1: "No 'Access-Control-Allow-Origin' header is present"

This error occurs when the server doesn't include the proper CORS headers or doesn't allow the requesting origin.

Solution:

  • Ensure allow_origins includes the domain making the request
  • For development, you might use allow_origins=["*"] (but avoid this in production)

Issue 2: Credentials not being sent

If your application uses cookies or authentication headers, but they're not being included in requests.

Solution:

  • Set allow_credentials=True in your CORS middleware
  • On the frontend, set credentials: 'include' in your fetch options
javascript
fetch('http://localhost:8000/api/items', {
credentials: 'include',
// other options
})

Issue 3: Custom headers not allowed

If you're using custom headers in your requests but getting CORS errors.

Solution:

  • Add the custom headers to the allow_headers list:
python
allow_headers=["Content-Type", "Authorization", "X-Custom-Header"]

Security Best Practices

While implementing CORS, keep these security practices in mind:

  1. Be specific with origins: Instead of using allow_origins=["*"], specify exactly which origins are allowed.

  2. Limit exposed methods: Only allow the HTTP methods your API actually needs.

  3. Be careful with credentials: Only set allow_credentials=True if you actually need to send cookies or authentication headers.

  4. Consider environment-specific configurations:

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

app = FastAPI()

# Configure CORS based on environment
if os.getenv("ENVIRONMENT") == "production":
origins = [
"https://www.myapp.com",
"https://app.myapp.com",
]
else:
origins = [
"http://localhost:3000",
"http://127.0.0.1:3000",
]

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

Summary

The FastAPI CORS Middleware provides a simple yet powerful way to control cross-origin requests to your API. Through this tutorial, we've learned:

  • What CORS is and why it's necessary for web security
  • How to implement the CORS middleware in FastAPI applications
  • The various configuration options for fine-tuning CORS behavior
  • How to handle CORS in real-world scenarios with separate frontend and backend
  • Common CORS issues and their solutions
  • Security best practices for CORS implementation

By properly implementing CORS in your FastAPI applications, you can ensure secure communication between your API and front-end applications while protecting against unauthorized cross-origin requests.

Exercises

  1. Create a FastAPI application that only allows GET requests from https://example.com and both GET and POST requests from https://admin.example.com.

  2. Configure a FastAPI application to allow requests from any subdomain of your website (e.g., *.mywebsite.com).

  3. Implement a FastAPI application with CORS that includes a custom header X-API-Key and test it with a simple HTML page that makes a fetch request.

  4. Create a FastAPI application that uses different CORS settings for different route prefixes (hint: look into FastAPI's APIRouter).

Additional Resources

Happy coding with FastAPI!



If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)