Cross‑Origin Resource Sharing (CORS) is a cornerstone of modern web development, allowing browsers to safely request resources from a different domain than the one that served the page. If you’re building APIs or web services with Python, mastering CORS configuration is essential to keep your applications secure while delivering a seamless user experience. In this guide we’ll walk through the fundamentals of CORS, show you how to configure it in the three most popular Python frameworks—Flask, Django, and FastAPI—and share practical tips for testing and troubleshooting.
What Is CORS and How Does It Work?
CORS is a browser‑level security feature that controls which origins (domains, protocols, or ports) are permitted to read resources from a server. When a web page makes a cross‑origin request, the browser sends a Origin header. The server then decides whether to include an Access-Control-Allow-Origin header in its response. If the header matches the requesting origin (or is a wildcard *), the browser allows the response to be accessed by JavaScript; otherwise, it blocks the data.
Key CORS response headers
- Access-Control-Allow-Origin: Specifies the allowed origin(s). Example:
Access-Control-Allow-Origin: https://example.com - Access-Control-Allow-Methods: Lists HTTP methods permitted for the request (GET, POST, PUT, DELETE, etc.).
- Access-Control-Allow-Headers: Indicates which custom headers the client may send.
- Access-Control-Allow-Credentials: When set to
true, cookies and HTTP authentication are allowed. - Access-Control-Max-Age: Caches the pre‑flight response for a specified number of seconds.
Why Configure CORS in Python Applications?
Without proper CORS handling, your API may either:
- Reject legitimate requests from trusted front‑ends, breaking functionality.
- Expose sensitive data to malicious sites if you use a permissive wildcard configuration.
Python frameworks provide middleware or extensions that automate header management, letting you focus on business logic while maintaining security best practices.
Configuring CORS in Flask
Flask is lightweight, and the most common way to handle CORS is with the flask‑cors extension.
Step‑by‑step setup
- Install the extension:
pip install flask-cors - Import and initialize it in your application:
from flask import Flask from flask_cors import CORS app = Flask(__name__) # Allow all origins (use with caution) CORS(app) # OR configure specific origins CORS(app, resources={ r"/api/*": {"origins": ["https://frontend.example.com", "https://admin.example.com"]}, r"/public/*": {"origins": "*"} }) - Fine‑tune individual routes if needed:
@app.route("/api/data") @cross_origin(origins="https://frontend.example.com", methods=["GET", "POST"]) def get_data(): return {"message": "CORS enabled for this endpoint"}
Best practices for Flask
- Never use
*forAccess-Control-Allow-OriginwhenAccess-Control-Allow-Credentialsistrue. - Restrict
methodsandheadersto the minimum required. - Separate public and private API namespaces and apply different CORS rules.
Configuring CORS in Django
Django’s built‑in middleware system makes CORS handling straightforward with the django-cors-headers package.
Installation and basic configuration
- Add the package:
pip install django-cors-headers - Update
settings.py:INSTALLED_APPS = [ ..., "corsheaders", ] MIDDLEWARE = [ "corsheaders.middleware.CorsMiddleware", "django.middleware.common.CommonMiddleware", ..., ] # Allow only specific origins CORS_ALLOWED_ORIGINS = [ "https://frontend.example.com", "https://admin.example.com", ] # Optional: allow all subdomains of a domain CORS_ALLOWED_ORIGIN_REGEXES = [ r"^https://\w+\.example\.com$", ] # Enable credentials if needed CORS_ALLOW_CREDENTIALS = True # Restrict methods CORS_ALLOW_METHODS = [ "GET", "POST", "PUT", "PATCH", "DELETE", ] - For per‑view control, use the decorator:
from corsheaders.decorators import cors_allow_all, cors_allow_origin @cors_allow_origin("https://frontend.example.com") def my_view(request): ...
Tips for Django projects
- Place
CorsMiddlewareas high as possible in the middleware list to ensure headers are added before other processing. - Combine
CORS_ALLOW_ALL_ORIGINS = Falsewith an explicit whitelist for tighter security. - Leverage
CORS_EXPOSE_HEADERSwhen you need the client to read custom response headers.
Configuring CORS in FastAPI
FastAPI, built on Starlette, uses a simple CORSMiddleware that can be added directly to the application instance.
Implementation steps
- Install FastAPI and Uvicorn (if not already):
pip install fastapi uvicorn - Add the middleware:
from fastapi import FastAPI from fastapi.middleware.cors import CORSMiddleware app = FastAPI() origins = [ "https://frontend.example.com", "https://admin.example.com", ] app.add_middleware( CORSMiddleware, allow_origins=origins, allow_credentials=True, allow_methods=["GET", "POST", "PUT", "DELETE"], allow_headers=["*"], # Adjust as needed max_age=600, ) - Define your routes normally; the middleware will handle CORS for all paths unless you create a separate
APIRouterwith its own settings.
FastAPI CORS checklist
- Use a list of origins instead of
*whenallow_credentials=True. - Set
max_ageto reduce pre‑flight traffic for high‑traffic endpoints. - When serving static files, remember that the same middleware applies unless you mount a separate
StaticFilesinstance with its own configuration.
Common Pitfalls and How to Avoid Them
Even seasoned developers can stumble over subtle CORS issues. Below are the most frequent mistakes and quick fixes.
1. Using a wildcard with credentials
Browsers reject responses that contain Access-Control-Allow-Credentials: true together with Access-Control-Allow-Origin: *. Always specify explicit origins when credentials are required.
2. Forgetting to handle pre‑flight OPTIONS requests
Some frameworks automatically respond to OPTIONS requests, but if you have custom routing logic you may need to add an explicit handler:
@app.route("/api/resource", methods=["OPTIONS"])
def options_handler():
response = make_response()
response.headers["Access-Control-Allow-Origin"] = "https://frontend.example.com"
response.headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE"
response.headers["Access-Control-Allow-Headers"] = "Content-Type,Authorization"
return response
3. Over‑exposing headers
Only expose the headers your client actually needs. Unnecessary exposure can leak internal implementation details.
4. Ignoring sub‑domain variations
If your front‑end uses dynamic sub‑domains (e.g., user1.example.com), use regex‑based origin matching instead of enumerating each sub‑domain.
Testing Your CORS Configuration
Automated testing ensures that CORS behaves as expected across environments.
Manual testing with browser dev tools
- Open the Network tab, make a cross‑origin request, and inspect the response headers.
- Look for
Access-Control-Allow-Originand verify it matches the request’sOrigin.
Leave a Reply