Cross‑origin resource sharing (CORS) is a security feature built into browsers that controls how web pages can request resources from a different domain. When you develop APIs with Python—whether you’re using Flask, Django, FastAPI, or any other framework—you’ll inevitably need to configure CORS correctly to enable legitimate front‑end clients while keeping malicious requests at bay. This guide walks you through the fundamentals of CORS, explains why proper configuration matters, and provides step‑by‑step code examples for the most popular Python web frameworks.
What Is CORS and Why Does It Matter?
CORS stands for Cross‑Origin Resource Sharing. By default, browsers enforce the same‑origin policy, which blocks a web page from making requests to a different domain, protocol, or port. CORS introduces a set of HTTP headers that tell the browser which origins are allowed to interact with your server.
- Access‑Control‑Allow-Origin: Specifies the permitted origin(s).
- Access‑Control‑Allow-Methods: Lists HTTP methods (GET, POST, etc.) that are allowed.
- Access‑Control‑Allow-Headers: Declares which custom headers can be sent.
- Access‑Control‑Allow-Credentials: Indicates whether cookies or HTTP authentication can be used.
Misconfiguring these headers can either break legitimate client applications or expose your API to cross‑site request forgery (CSRF) attacks. A well‑crafted CORS setup balances security with usability—exactly what this guide aims to achieve.
General Best Practices for Python CORS Configuration
1. Keep the Allowed Origins List Tight
Never use a wildcard (*) in production unless you are serving completely public data. Instead, enumerate the exact domains that need access.
2. Limit HTTP Methods and Headers
Only enable the methods and headers your API truly requires. Over‑permissive settings increase the attack surface.
3>Use HTTPS Everywhere
When Access-Control-Allow-Credentials is set to true, browsers will only send credentials over secure connections. Enforce HTTPS to protect user data.
4>Leverage Framework‑Specific Middleware
Most Python web frameworks provide dedicated CORS middleware or extensions. These tools automatically add the correct headers and handle pre‑flight (OPTIONS) requests, saving you from writing low‑level code.
CORS Configuration in Popular Python Frameworks
Flask
Flask does not include CORS support out of the box, but the flask‑cors extension makes it straightforward.
pip install flask-cors
Below is a minimal Flask app with a secure CORS setup:
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
# Define a whitelist of allowed origins
allowed_origins = [
"https://example.com",
"https://admin.example.com"
]
# Apply CORS with fine‑grained options
CORS(
app,
resources={r"/api/*": {"origins": allowed_origins}},
methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Content-Type", "Authorization"],
supports_credentials=True
)
@app.route("/api/data")
def get_data():
return jsonify({"message": "CORS is configured!"})
if __name__ == "__main__":
app.run(ssl_context="adhoc")
Key points:
- The
resourcesdictionary limits CORS to routes that start with/api/. supports_credentials=Trueenables cookies and HTTP authentication.- Running with
ssl_context="adhoc"forces HTTPS during development.
Django
Django’s django-cors-headers package is the de‑facto solution for handling CORS.
pip install django-cors-headers
After installing, add the middleware and configure settings in settings.py:
# settings.py
INSTALLED_APPS = [
# …
"corsheaders",
# …
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
# …
]
# Only allow specific origins
CORS_ALLOWED_ORIGINS = [
"https://example.com",
"https://dashboard.example.com",
]
# If you need to allow subdomains
# CORS_ALLOWED_ORIGIN_REGEXES = [r"^https://.*\.example\.com$"]
CORS_ALLOW_METHODS = [
"GET",
"POST",
"PUT",
"PATCH",
"DELETE",
]
CORS_ALLOW_HEADERS = [
"content-type",
"authorization",
"x-csrftoken",
]
CORS_ALLOW_CREDENTIALS = True
Notice the separation between CORS_ALLOWED_ORIGINS and CORS_ALLOWED_ORIGIN_REGEXES. Use the regex option only when you truly need wildcard subdomains; otherwise, list each origin explicitly.
FastAPI
FastAPI includes built‑in support for CORS via starlette.middleware.cors.CORSMiddleware. Because FastAPI is asynchronous, the configuration is concise.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"https://example.com",
"https://app.example.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
allow_credentials=True,
)
@app.get("/items/")
async def read_items():
return {"message": "CORS is active"}
FastAPI automatically handles the pre‑flight OPTIONS request, so you don’t need extra code for that.
Other Python Frameworks
If you work with AIOHTTP, Tornado, or Sanic, the principle remains the same: add a middleware that injects the appropriate CORS headers. Below is a quick example for AIOHTTP.
from aiohttp import web
async def cors_middleware(app, handler):
async def middleware(request):
response = await handler(request)
response.headers["Access-Control-Allow-Origin"] = "https://example.com"
response.headers["Access-Control-Allow-Methods"] = "GET,POST,PUT,DELETE"
response.headers["Access-Control-Allow-Headers"] = "Content-Type,Authorization"
response.headers["Access-Control-Allow-Credentials"] = "true"
return response
return middleware
app = web.Application(middlewares=[cors_middleware])
# … define routes …
web.run_app(app, ssl_context=... ) # enforce HTTPS
Testing Your CORS Setup
After configuring CORS, verify that the headers are correctly sent. Use one of the following methods:
- Browser DevTools: Open the Network tab, make a request to your API, and inspect the response headers.
- cURL with the
-Iflag to view headers only:curl -I -H "Origin: https://example.com" https://api.example.com/api/data - Postman or Insomnia: Set an
Originheader manually and check the response.
Look for the exact values you defined (e.g., Access-Control-Allow-Origin: https://example.com) and ensure that Access-Control-Allow-Credentials is present only when you intend to send cookies or authentication tokens.
Common Pitfalls and How to Avoid Them
- Wildcard Origin with Credentials: Browsers reject
Access-Control-Allow-Origin: *whenAccess-Control-Allow-Credentials: true. Always list explicit origins when credentials are involved. - Missing Pre‑flight Handling: Forgetting to allow the
OPTIONSmethod will cause browsers to block complex requests. Ensureallow_methodsincludesOPTIONSor let the middleware add it automatically. - Overly Broad Header List: Adding every possible header (e.g.,
*) defeats the purpose of CORS. List only the headers your client truly needs. - Running on HTTP in Production: Even if CORS headers are correct, transmitting credentials over plain HTTP exposes them to sniffing. Enforce HTTPS with a valid TLS certificate.
- Environment‑Specific Settings: Hard‑code origins in source code and forget to adjust for staging or development. Use environment variables or a configuration file to keep origins flexible.
Dynamic Origin Whitelisting (Advanced)
In some cases you may need to allow origins that are stored in a database or fetched from an external service. Below is a Flask example that demonstrates a dynamic check.
from flask import Flask, request, jsonify
from flask_cors import CORS
app = Flask(__name__)
def get_allowed_origins():
# Imagine this pulls from a DB or config service
return ["https://example.com", "https://partner.example.org"]
def custom_cors_origin():
origin = request.headers.get("Origin")
Leave a Reply