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
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.
# 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:
# Allow all subdomains of myapp.com
allow_origin_regex="https://.*\.myapp\.com"
allow_methods
Specifies which HTTP methods are allowed:
# Allow specific methods
allow_methods=["GET", "POST"]
# Allow all methods
allow_methods=["*"]
allow_headers
Controls which HTTP headers can be used during the request:
# 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):
# Allow credentials
allow_credentials=True
# Don't allow credentials
allow_credentials=False
expose_headers
Specifies which headers should be exposed to the browser:
expose_headers=["Content-Length", "X-Custom-Header"]
max_age
Defines how long the results of a preflight request can be cached (in seconds):
max_age=600 # Cache for 10 minutes
Complete Example with All Options
Here's a more comprehensive example showing all available options:
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:
-
For simple requests (GET, POST with certain content types), the browser adds an
Origin
header to the request. -
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.
-
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)
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)
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:
- The FastAPI backend runs on port 8000
- The React frontend runs on port 3000
- The CORS middleware allows requests from the frontend origin
- 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
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:
allow_headers=["Content-Type", "Authorization", "X-Custom-Header"]
Security Best Practices
While implementing CORS, keep these security practices in mind:
-
Be specific with origins: Instead of using
allow_origins=["*"]
, specify exactly which origins are allowed. -
Limit exposed methods: Only allow the HTTP methods your API actually needs.
-
Be careful with credentials: Only set
allow_credentials=True
if you actually need to send cookies or authentication headers. -
Consider environment-specific configurations:
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
-
Create a FastAPI application that only allows GET requests from
https://example.com
and both GET and POST requests fromhttps://admin.example.com
. -
Configure a FastAPI application to allow requests from any subdomain of your website (e.g.,
*.mywebsite.com
). -
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. -
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! :)