Managing CORS in Python REST APIs

Cross-Origin Resource Sharing (CORS) is a critical browser security mechanism that restricts client-side web applications from making HTTP requests to a domain different from the one serving the front end. In Python, managing CORS in RESTful APIs requires instructing the backend framework to send specific HTTP response headers that explicitly grant permission to external origins. This article explains how CORS functions at a protocol level and demonstrates how Python frameworks—primarily FastAPI, Flask, and Django—implement and handle cross-origin traffic effectively.

How CORS Works in RESTful APIs

CORS is enforced entirely by the client's web browser, not the server. When a web application attempts to fetch resources from a different domain, port, or protocol, the browser initiates a CORS handshake.

For complex HTTP methods (such as PUT, DELETE, or POST with application/json), the browser first issues an automated preflight request using the OPTIONS method. The Python backend must intercept this preflight request and respond with specific access-control headers:

If the backend does not return the appropriate headers, the browser blocks the incoming response from being read by the client application.

Managing CORS in FastAPI

FastAPI provides native, highly optimized support for CORS through Starlette's built-in CORSMiddleware. Because it operates at the ASGI middleware level, it automatically intercepts incoming preflight OPTIONS requests before they reach your route handlers.

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

app = FastAPI()

origins = [
    "https://example.com",
    "http://localhost:3000",
]

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

@app.get("/api/data")
def read_data():
    return {"status": "success"}

Managing CORS in Flask

Flask does not support CORS out of the box. The standard approach is to use the flask-cors package, a dedicated WSGI extension that applies CORS headers globally or to specific routes and blueprints.

from flask import Flask, jsonify
from flask_cors import CORS

app = Flask(__name__)

# Apply CORS globally to specific origins
CORS(app, resources={r"/api/*": {"origins": ["https://example.com", "http://localhost:3000"]}})

@app.route("/api/data", methods=["GET"])
def get_data():
    return jsonify({"status": "success"})

flask-cors automatically responds to preflight OPTIONS requests for matching paths without requiring dedicated route definitions.

Managing CORS in Django

In Django and Django REST Framework (DRF), CORS is best managed using the third-party library django-cors-headers. This library provides middleware that attaches the necessary headers directly to outgoing Django HttpResponse objects.

Once installed, it is configured in settings.py:

INSTALLED_APPS = [
    ...,
    "corsheaders",
    "rest_framework",
]

MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",  # Must be placed as high as possible
    "django.middleware.common.CommonMiddleware",
    ...,
]

# Specify allowed origins
CORS_ALLOWED_ORIGINS = [
    "https://example.com",
    "http://localhost:3000",
]

CORS_ALLOW_CREDENTIALS = True

Best Practices for Production

  1. Avoid Wildcards with Sensitive Data: Using allow_origins=["*"] allows any website to make requests to your API. Never pair a wildcard origin with Access-Control-Allow-Credentials: true, as browsers will reject the response for security reasons.
  2. Handle Preflight Caching: Configure the Access-Control-Max-Age header (supported by all major Python CORS tools) to cache preflight responses in the browser, reducing unnecessary OPTIONS round trips.
  3. Position Middleware Correctly: Ensure CORS middleware is placed near the top of the middleware stack so it runs before authentication, logging, or error-handling components that might terminate the request early.