Python Cors Configuration Guide

Written by

in

Cross‑origin resource sharing (CORS) is a browser security feature that controls how web pages can request resources from a different domain. If you’ve ever built a Python API that serves data to a JavaScript front‑end, you’ve probably hit the dreaded “No ‘Access‑Control‑Allow‑Origin’ header” error. This guide walks you through everything you need to know to configure CORS correctly in Python—whether you’re using Flask, Django, FastAPI, or a custom WSGI app. By the end, you’ll be able to secure your endpoints, avoid common pitfalls, and keep your API SEO‑friendly.

What Is CORS and Why Does It Matter?

CORS stands for Cross‑Origin Resource Sharing. It tells browsers which origins (domains, schemes, or ports) are allowed to read responses from your server. Without proper CORS headers, browsers block the response, even though the request technically reaches the server. This is crucial for:

  • Protecting sensitive data from being exposed to malicious sites.
  • Enabling legitimate cross‑domain interactions, such as a React app on example.com consuming an API on api.example.com.
  • Improving SEO by ensuring search engine crawlers can access public API endpoints when needed.

Core CORS Headers You Need to Know

  • Access-Control-Allow-Origin – Specifies the allowed origin(s). Use * for public APIs, but prefer explicit origins for security.
  • Access-Control-Allow-Methods – Lists HTTP methods (GET, POST, PUT, DELETE, etc.) the server permits.
  • Access-Control-Allow-Headers – Indicates which custom headers the client may send.
  • Access-Control-Allow-Credentials – When set to true, browsers expose cookies and HTTP authentication to the client.
  • Access-Control-Max-Age – Caches preflight responses for a specified number of seconds.

Configuring CORS in Popular Python Frameworks

Flask – Using flask-cors

Flask doesn’t include CORS support out of the box, but the flask-cors extension makes it painless.

pip install flask-cors

Basic usage:

from flask import Flask, jsonify
from flask_cors import CORS

app = Flask(__name__)

# Allow all origins (not recommended for production)
CORS(app)

@app.route('/api/data')
def data():
    return jsonify({'message': 'Hello, CORS!'})

Fine‑tune the configuration:

# Only allow specific origins and methods
CORS(app,
     resources={r"/api/*": {"origins": ["https://example.com", "https://admin.example.com"]}},
     methods=["GET", "POST"],
     allow_headers=["Content-Type", "Authorization"],
     supports_credentials=True)

Django – Using django-cors-headers

Django projects typically handle CORS with the django-cors-headers package.

pip install django-cors-headers

Add it to INSTALLED_APPS and the middleware stack:

# settings.py
INSTALLED_APPS = [
    ...,
    'corsheaders',
]

MIDDLEWARE = [
    'corsheaders.middleware.CorsMiddleware',
    'django.middleware.common.CommonMiddleware',
    ...,
]

# Allow only trusted origins
CORS_ALLOWED_ORIGINS = [
    "https://example.com",
    "https://sub.example.com",
]

# Optional: expose custom headers to the client
CORS_EXPOSE_HEADERS = ["X-My-Custom-Header"]

For a public API, you can use CORS_ALLOW_ALL_ORIGINS = True, but remember to tighten it before going live.

FastAPI – Built‑in CORS Support

FastAPI includes CORS handling via starlette.middleware.cors.CORSMiddleware. No extra package is needed.

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=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["*"],  # or list specific headers
)

@app.get("/items/")
async def read_items():
    return {"message": "FastAPI CORS configured!"}

Manual CORS with WSGI or ASGI

If you’re building a lightweight API without a full framework, you can add headers manually.

# Example using a simple WSGI app
def application(environ, start_response):
    # Handle preflight request
    if environ['REQUEST_METHOD'] == 'OPTIONS':
        headers = [
            ('Access-Control-Allow-Origin', '*'),
            ('Access-Control-Allow-Methods', 'GET,POST,OPTIONS'),
            ('Access-Control-Allow-Headers', 'Content-Type,Authorization'),
            ('Access-Control-Max-Age', '86400'),
        ]
        start_response('204 No Content', headers)
        return [b'']

    # Normal request handling
    response_body = b'{"message":"Hello from WSGI"}'
    headers = [
        ('Content-Type', 'application/json'),
        ('Access-Control-Allow-Origin', '*'),  # adjust for production
    ]
    start_response('200 OK', headers)
    return [response_body]

Best Practices for Secure CORS Configuration

  • Never use * in production when you also enable Access-Control-Allow-Credentials. Browsers will reject the response.
  • Whitelist only the domains that truly need access. Use environment variables to keep the list flexible across dev, staging, and prod.
  • Limit Access-Control-Allow-Methods to the HTTP verbs you actually support.
  • Specify Access-Control-Allow-Headers explicitly instead of using * whenever possible.
  • Set a reasonable Access-Control-Max-Age (e.g., 600 seconds) to reduce preflight traffic without caching stale policies.
  • Log CORS failures during development. Many frameworks expose a debug flag that prints the missing header.

Testing Your CORS Setup

Before deploying, verify that your configuration works as expected:

  1. Browser DevTools: Open the Network tab, make a request from a different origin, and check the response headers for the correct CORS fields.
  2. cURL: Use the -H "Origin: https://example.com" flag to simulate a cross‑origin request.
    curl -i -H "Origin: https://example.com" https://api.example.com/data
  3. Online tools: Services like test‑cors.org let you paste an endpoint and see the CORS response instantly.
  4. Automated tests: Include a test case in your CI pipeline that asserts the presence of Access-Control-Allow-Origin for a known origin.

SEO Considerations for Public APIs

Search engines occasionally crawl public API endpoints, especially when the data is used for rich snippets or structured data. To keep your API SEO‑friendly:

  • Return 200 OK with proper Content-Type (e.g., application/json) for GET requests.
  • Include Access-Control-Allow-Origin: * only for endpoints you intend to be publicly indexed.
  • Avoid excessive redirects; they can dilute page rank and increase crawl latency.
  • Provide a robots.txt file that explicitly allows or disallows crawling of API paths.

Common Pitfalls and How to Fix Them

1. “No ‘Access‑Control‑Allow‑Origin’ header” error

Usually caused by forgetting to enable CORS on the specific route or forgetting to handle OPTIONS preflight requests. Ensure your middleware is applied before route definitions and that OPTIONS is allowed.

2. Using * with credentials

Browsers reject the response. Switch to an explicit origin list and set supports_credentials=True (Flask) or allow_credentials=True (FastAPI).

3. Forgetting to expose custom headers

If your client reads a header like X-Total-Count, you must add it to Access-Control-Expose-Headers (Django) or expose_headers (FastAPI).

4. Over‑permissive CORS in production

Audit your configuration before launch. A quick script can compare your dev settings with production variables to catch accidental * usage.

Quick Reference Cheat Sheet

# Flask
from flask_cors import CORS
CORS(app, origins=["https://example.com"], supports_credentials=True)

# Django (settings.py)
CORS_ALLOWED_ORIGINS = ["https://example.com"]
CORS_ALLOW_CREDENTIALS = True

Comments

Leave a Reply

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