Cross‑Origin Resource Sharing (CORS) can feel like a mysterious wall that blocks legitimate requests from your front‑end JavaScript to a Python back‑end API. If you’ve ever seen the dreaded “No ‘Access‑Control‑Allow‑Origin’ header is present” error in the browser console, you know how frustrating it can be. In this guide we’ll demystify CORS for Python developers, walk through the most common frameworks—Flask, Django, FastAPI, and plain WSGI—and provide ready‑to‑copy code snippets that let you configure secure, flexible, and SEO‑friendly CORS policies in just a few minutes.
What Is CORS and Why Does It Matter?
CORS is a browser security mechanism that controls which origins (domains, ports, or schemes) are allowed to read resources from a different origin. It works by adding special HTTP response headers such as Access-Control-Allow-Origin, Access-Control-Allow-Methods, and Access-Control-Allow-Headers. When a browser sends a preflight request (an OPTIONS call), the server must respond with the appropriate headers; otherwise the actual request is blocked.
From an SEO perspective, a well‑configured API that returns the correct CORS headers improves crawlability for headless browsers and reduces the risk of “soft 404” errors caused by blocked resources. It also signals to search engines that your site follows modern web standards, which can indirectly boost rankings.
Choosing the Right CORS Library for Python
While you can manually add the required headers, using a dedicated library saves time and reduces bugs. Below is a quick comparison of the most popular options:
- Flask‑CORS – Simple decorator‑based approach, works with any Flask app.
- django‑cors‑headers – Middleware that integrates seamlessly with Django’s request/response cycle.
- FastAPI – Built‑in support via
fastapi.middleware.cors.CORSMiddleware. - WSGI Middleware – For raw WSGI applications, a tiny middleware can be added without any framework.
Step‑by‑Step CORS Configuration in Flask
1. Install Flask‑CORS
pip install Flask-Cors
2. Apply the CORS Extension
Below is a minimal Flask app that enables CORS for all routes and origins. For production, you’ll want to limit the origins list to trusted domains.
from flask import Flask, jsonify
from flask_cors import CORS
app = Flask(__name__)
# Enable CORS for the entire app
CORS(app, resources={r"/api/*": {"origins": "*"}})
@app.route('/api/hello')
def hello():
return jsonify(message='Hello from Flask!')
if __name__ == '__main__':
app.run(debug=True)
3. Fine‑Tuning Options
- origins: a list or regex of allowed origins (e.g.,
["https://example.com", "https://sub.domain.com"]). - methods: HTTP methods you wish to expose (
["GET", "POST", "PUT", "DELETE"]). - allow_headers: custom request headers the client can send.
- expose_headers: response headers that browsers are allowed to read.
Example with tighter security:
CORS(app,
resources={r"/api/*": {"origins": ["https://myfrontend.com"]}},
methods=["GET", "POST"],
allow_headers=["Content-Type", "Authorization"],
expose_headers=["X-My-Custom-Header"])
Configuring CORS in Django
1. Install django‑cors‑headers
pip install django-cors-headers
2. Add Middleware and Settings
Update settings.py as follows:
# settings.py
INSTALLED_APPS = [
# …
"corsheaders",
# …
]
MIDDLEWARE = [
"corsheaders.middleware.CorsMiddleware",
"django.middleware.common.CommonMiddleware",
# …
]
# Allow only specific origins
CORS_ALLOWED_ORIGINS = [
"https://myfrontend.com",
"https://admin.myfrontend.com",
]
# Optional: expose custom headers
CORS_EXPOSE_HEADERS = ["X-Total-Count"]
3. Testing the Configuration
Run the development server and issue a request from a different origin (e.g., using curl -H "Origin: https://evil.com" ...). You should see the Access-Control-Allow-Origin header only when the origin matches one of the entries in CORS_ALLOWED_ORIGINS.
FastAPI: Built‑In CORS Middleware
FastAPI’s design already embraces modern API practices, and CORS is no exception. The following example shows a complete FastAPI app with CORS enabled for a whitelist of domains.
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
app = FastAPI()
origins = [
"https://myfrontend.com",
"https://admin.myfrontend.com",
]
app.add_middleware(
CORSMiddleware,
allow_origins=origins,
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["*"],
)
@app.get("/items/")
async def read_items():
return {"message": "FastAPI CORS is working!"}
Custom CORS Middleware for Plain WSGI Applications
If you’re not using a full‑stack framework, you can still add CORS support by wrapping your WSGI app with a tiny middleware class.
class CORSMiddleware:
def __init__(self, app, origins="*"):
self.app = app
self.origins = origins
def __call__(self, environ, start_response):
def custom_start_response(status, headers, exc_info=None):
headers.append(("Access-Control-Allow-Origin", self.origins))
headers.append(("Access-Control-Allow-Methods", "GET,POST,OPTIONS"))
headers.append(("Access-Control-Allow-Headers", "Content-Type,Authorization"))
return start_response(status, headers, exc_info)
# Handle preflight requests directly
if environ["REQUEST_METHOD"] == "OPTIONS":
custom_start_response("200 OK", [])
return [b""]
return self.app(environ, custom_start_response)
# Example usage with a simple app
def simple_app(environ, start_response):
start_response("200 OK", [("Content-Type", "text/plain")])
return [b"Hello from WSGI!"]
application = CORSMiddleware(simple_app, origins="https://myfrontend.com")
Best Practices for Secure CORS Policies
- Never use
*in production unless the resource is truly public (e.g., static assets). - Restrict
Access-Control-Allow-Methodsto only the HTTP verbs you actually need. - Validate the
Originheader on the server side to prevent header spoofing. - Combine CORS with other security layers such as CSRF tokens, authentication headers, and rate limiting.
- Log CORS failures during development; they provide valuable insight into misconfigured origins.
Testing and Debugging CORS Issues
Even with the correct configuration, browsers may still block requests due to cached headers or mismatched case sensitivity. Use these tools to verify your setup:
- Browser DevTools – Check the “Network” tab for the
Access-Control-*headers in the response. - cURL – Simulate a preflight request:
curl -i -X OPTIONS https://api.example.com/resource \\ -H "Origin: https://myfrontend.com" \\ -H "Access-Control-Request-Method: POST" \\ -H "Access-Control-Request-Headers: Content-Type,Authorization" - Online Validators – Websites like Test CORS let you input a URL and see the exact header set.
Conclusion
Configuring CORS in Python doesn’t have to be a headache. Whether you’re building a lightweight Flask micro‑service, a full‑featured Django site, or a high‑performance FastAPI backend, the right library and a clear whitelist of origins will keep your API both secure and accessible. Remember to avoid open‑ended * policies in production, test with real browsers, and keep your documentation up to date for future developers. With the snippets and best‑practice checklist above, you’re now equipped to implement a robust CORS strategy that satisfies both developers and search‑engine crawlers alike.
Leave a Reply