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.comconsuming an API onapi.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 totrue, 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 enableAccess-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-Methodsto the HTTP verbs you actually support. - Specify
Access-Control-Allow-Headersexplicitly 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
debugflag that prints the missing header.
Testing Your CORS Setup
Before deploying, verify that your configuration works as expected:
- Browser DevTools: Open the Network tab, make a request from a different origin, and check the response headers for the correct CORS fields.
- 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 - Online tools: Services like test‑cors.org let you paste an endpoint and see the CORS response instantly.
- Automated tests: Include a test case in your CI pipeline that asserts the presence of
Access-Control-Allow-Originfor 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 OKwith properContent-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.txtfile 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
Leave a Reply