Python Cors Configuration Guide

Written by

in

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

  1. Install the extension:
    pip install flask-cors
  2. 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": "*"}
    })
  3. 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 * for Access-Control-Allow-Origin when Access-Control-Allow-Credentials is true.
  • Restrict methods and headers to 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

  1. Add the package:
    pip install django-cors-headers
  2. 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",
    ]
  3. 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 CorsMiddleware as high as possible in the middleware list to ensure headers are added before other processing.
  • Combine CORS_ALLOW_ALL_ORIGINS = False with an explicit whitelist for tighter security.
  • Leverage CORS_EXPOSE_HEADERS when 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

  1. Install FastAPI and Uvicorn (if not already):
    pip install fastapi uvicorn
  2. 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,
    )
  3. Define your routes normally; the middleware will handle CORS for all paths unless you create a separate APIRouter with its own settings.

FastAPI CORS checklist

  • Use a list of origins instead of * when allow_credentials=True.
  • Set max_age to reduce pre‑flight traffic for high‑traffic endpoints.
  • When serving static files, remember that the same middleware applies unless you mount a separate StaticFiles instance 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-Origin and verify it matches the request’s Origin.

Comments

Leave a Reply

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