Python Cors Configuration Guide

Written by

in

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 resources dictionary limits CORS to routes that start with /api/.
  • supports_credentials=True enables 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:

  1. Browser DevTools: Open the Network tab, make a request to your API, and inspect the response headers.
  2. cURL with the -I flag to view headers only:
    curl -I -H "Origin: https://example.com" https://api.example.com/api/data
  3. Postman or Insomnia: Set an Origin header 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: * when Access-Control-Allow-Credentials: true. Always list explicit origins when credentials are involved.
  • Missing Pre‑flight Handling: Forgetting to allow the OPTIONS method will cause browsers to block complex requests. Ensure allow_methods includes OPTIONS or 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")

Comments

Leave a Reply

Your email address will not be published. Required fields are marked *