Cross-Origin Resource Sharing (CORS) is essential when building modern web applications where frontend and backend are served from different origins. This chapter covers CORS configuration in Django, security best practices, and troubleshooting common issues when integrating with frontend frameworks.
CORS is a security mechanism implemented by web browsers that restricts web pages from making requests to a different domain, protocol, or port than the one serving the web page. This same-origin policy prevents malicious scripts from accessing sensitive data across origins.
CORS configuration is required when:
Key CORS headers that control cross-origin access:
Response Headers (Server-side):
Access-Control-Allow-Origin: Specifies allowed originsAccess-Control-Allow-Methods: Allowed HTTP methodsAccess-Control-Allow-Headers: Allowed request headersAccess-Control-Allow-Credentials: Whether credentials are allowedAccess-Control-Max-Age: Preflight cache durationRequest Headers (Client-side):
Origin: The origin making the requestAccess-Control-Request-Method: Method for preflight requestsAccess-Control-Request-Headers: Headers for preflight requestspip install django-cors-headers
# requirements.txt
django-cors-headers==4.3.1
# settings.py
INSTALLED_APPS = [
# ... other apps
'corsheaders',
# ... rest of apps
]
MIDDLEWARE = [
# ... other middleware
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
# ... rest of middleware
]
# CORS settings
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000", # React dev server
"http://127.0.0.1:3000", # Alternative localhost
"https://myapp.com", # Production frontend
"https://www.myapp.com", # Production frontend with www
]
# Alternative: Allow all origins (DEVELOPMENT ONLY)
# CORS_ALLOW_ALL_ORIGINS = True
# Allowed methods
CORS_ALLOWED_METHODS = [
'DELETE',
'GET',
'OPTIONS',
'PATCH',
'POST',
'PUT',
]
# Allowed headers
CORS_ALLOWED_HEADERS = [
'accept',
'accept-encoding',
'authorization',
'content-type',
'dnt',
'origin',
'user-agent',
'x-csrftoken',
'x-requested-with',
]
# Allow credentials (cookies, authorization headers)
CORS_ALLOW_CREDENTIALS = True
# Preflight cache duration (in seconds)
CORS_PREFLIGHT_MAX_AGE = 86400 # 24 hours
# settings/development.py
from .base import *
# Development CORS settings
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://127.0.0.1:3000",
"http://localhost:8080", # Vue dev server
]
# More permissive in development
CORS_ALLOW_ALL_ORIGINS = True
CORS_ALLOW_CREDENTIALS = True
# Debug CORS issues
CORS_REPLACE_HTTPS_REFERER = True
# settings/production.py
from .base import *
import os
# Production CORS settings
CORS_ALLOWED_ORIGINS = [
"https://myapp.com",
"https://www.myapp.com",
"https://app.myapp.com",
]
# Strict settings for production
CORS_ALLOW_ALL_ORIGINS = False
CORS_ALLOW_CREDENTIALS = True
# Security headers
SECURE_CROSS_ORIGIN_OPENER_POLICY = "same-origin"
SECURE_REFERRER_POLICY = "same-origin"
# settings.py
import os
def get_cors_allowed_origins():
"""Get CORS origins from environment variable."""
origins_env = os.environ.get('CORS_ALLOWED_ORIGINS', '')
if origins_env:
return [origin.strip() for origin in origins_env.split(',')]
# Default origins
if DEBUG:
return [
"http://localhost:3000",
"http://127.0.0.1:3000",
]
else:
return [
"https://myapp.com",
"https://www.myapp.com",
]
CORS_ALLOWED_ORIGINS = get_cors_allowed_origins()
# Custom CORS origin regex (for subdomains)
CORS_ALLOWED_ORIGIN_REGEXES = [
r"^https://\w+\.myapp\.com$", # Allow all subdomains
]
# middleware/cors.py
from django.http import HttpResponse
from django.utils.deprecation import MiddlewareMixin
import re
class CustomCorsMiddleware(MiddlewareMixin):
"""Custom CORS middleware with advanced logic."""
def __init__(self, get_response):
self.get_response = get_response
super().__init__(get_response)
def process_request(self, request):
"""Handle preflight requests."""
if request.method == 'OPTIONS':
response = HttpResponse()
return self.add_cors_headers(request, response)
return None
def process_response(self, request, response):
"""Add CORS headers to all responses."""
return self.add_cors_headers(request, response)
def add_cors_headers(self, request, response):
"""Add appropriate CORS headers."""
origin = request.META.get('HTTP_ORIGIN')
if self.is_origin_allowed(origin):
response['Access-Control-Allow-Origin'] = origin
response['Access-Control-Allow-Credentials'] = 'true'
# Handle preflight requests
if request.method == 'OPTIONS':
response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, PATCH, DELETE, OPTIONS'
response['Access-Control-Allow-Headers'] = (
'Accept, Accept-Encoding, Authorization, Content-Type, '
'Origin, User-Agent, X-CSRFToken, X-Requested-With'
)
response['Access-Control-Max-Age'] = '86400'
return response
def is_origin_allowed(self, origin):
"""Check if origin is allowed."""
if not origin:
return False
# Check exact matches
allowed_origins = getattr(settings, 'CORS_ALLOWED_ORIGINS', [])
if origin in allowed_origins:
return True
# Check regex patterns
allowed_regexes = getattr(settings, 'CORS_ALLOWED_ORIGIN_REGEXES', [])
for pattern in allowed_regexes:
if re.match(pattern, origin):
return True
return False
# views/api.py
from django.http import JsonResponse
from django.views.decorators.csrf import csrf_exempt
from django.views.decorators.http import require_http_methods
from django.utils.decorators import method_decorator
from django.views import View
def cors_headers(origin_patterns=None):
"""Decorator to add CORS headers to specific views."""
def decorator(view_func):
def wrapped_view(request, *args, **kwargs):
response = view_func(request, *args, **kwargs)
origin = request.META.get('HTTP_ORIGIN')
if origin and (not origin_patterns or any(
re.match(pattern, origin) for pattern in origin_patterns
)):
response['Access-Control-Allow-Origin'] = origin
response['Access-Control-Allow-Credentials'] = 'true'
return response
return wrapped_view
return decorator
@cors_headers(['https://.*\.myapp\.com'])
@require_http_methods(['GET', 'POST'])
def api_endpoint(request):
"""API endpoint with custom CORS configuration."""
return JsonResponse({'message': 'Success'})
class CorsApiView(View):
"""Base API view with CORS support."""
allowed_origins = []
def dispatch(self, request, *args, **kwargs):
# Handle preflight requests
if request.method == 'OPTIONS':
response = JsonResponse({})
return self.add_cors_headers(request, response)
response = super().dispatch(request, *args, **kwargs)
return self.add_cors_headers(request, response)
def add_cors_headers(self, request, response):
"""Add CORS headers to response."""
origin = request.META.get('HTTP_ORIGIN')
if self.is_origin_allowed(origin):
response['Access-Control-Allow-Origin'] = origin
response['Access-Control-Allow-Credentials'] = 'true'
response['Access-Control-Allow-Methods'] = 'GET, POST, PUT, DELETE, OPTIONS'
response['Access-Control-Allow-Headers'] = (
'Accept, Authorization, Content-Type, X-CSRFToken'
)
return response
def is_origin_allowed(self, origin):
"""Check if origin is allowed for this view."""
if not origin:
return False
for pattern in self.allowed_origins:
if re.match(pattern, origin):
return True
return False
# settings.py - Restrictive CORS configuration
CORS_ALLOWED_ORIGINS = [
"https://app.mycompany.com", # Only specific domains
]
# Don't use wildcards in production
# CORS_ALLOW_ALL_ORIGINS = False # Never True in production
# Limit allowed methods
CORS_ALLOWED_METHODS = [
'GET',
'POST',
'PUT',
'DELETE',
]
# Limit allowed headers
CORS_ALLOWED_HEADERS = [
'accept',
'authorization',
'content-type',
'x-csrftoken',
]
# Be careful with credentials
CORS_ALLOW_CREDENTIALS = True # Only if necessary
# settings/base.py
import os
# CORS configuration based on environment
if os.environ.get('ENVIRONMENT') == 'production':
CORS_ALLOWED_ORIGINS = [
"https://myapp.com",
"https://www.myapp.com",
]
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = False
elif os.environ.get('ENVIRONMENT') == 'staging':
CORS_ALLOWED_ORIGINS = [
"https://staging.myapp.com",
"https://preview.myapp.com",
]
CORS_ALLOW_CREDENTIALS = True
CORS_ALLOW_ALL_ORIGINS = False
else: # Development
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
"http://127.0.0.1:3000",
]
CORS_ALLOW_CREDENTIALS = True
# More permissive for development
CORS_ALLOW_ALL_ORIGINS = DEBUG
# settings.py
# CSP headers that work with CORS
SECURE_CONTENT_TYPE_NOSNIFF = True
SECURE_BROWSER_XSS_FILTER = True
X_FRAME_OPTIONS = 'DENY'
# CSP configuration
CSP_DEFAULT_SRC = ["'self'"]
CSP_SCRIPT_SRC = ["'self'", "'unsafe-inline'", "https://cdn.myapp.com"]
CSP_STYLE_SRC = ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"]
CSP_FONT_SRC = ["'self'", "https://fonts.gstatic.com"]
CSP_IMG_SRC = ["'self'", "data:", "https:"]
CSP_CONNECT_SRC = ["'self'", "https://api.myapp.com"]
// utils/api.js
import axios from 'axios';
const api = axios.create({
baseURL: process.env.REACT_APP_API_URL || 'http://localhost:8000/api',
withCredentials: true, // Include cookies in requests
headers: {
'Content-Type': 'application/json',
}
});
// Request interceptor for CSRF token
api.interceptors.request.use((config) => {
const csrfToken = getCsrfToken();
if (csrfToken) {
config.headers['X-CSRFToken'] = csrfToken;
}
return config;
});
// Response interceptor for CORS errors
api.interceptors.response.use(
(response) => response,
(error) => {
if (error.message === 'Network Error') {
console.error('CORS error or network issue:', error);
// Handle CORS errors gracefully
}
return Promise.reject(error);
}
);
function getCsrfToken() {
// Get CSRF token from cookie or meta tag
const cookieValue = document.cookie
.split('; ')
.find(row => row.startsWith('csrftoken='))
?.split('=')[1];
return cookieValue || document.querySelector('[name=csrfmiddlewaretoken]')?.value;
}
export default api;
// utils/fetchApi.js
class ApiClient {
constructor(baseURL) {
this.baseURL = baseURL;
}
async request(endpoint, options = {}) {
const url = `${this.baseURL}${endpoint}`;
const defaultOptions = {
credentials: 'include', // Include cookies
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': this.getCsrfToken(),
}
};
const config = { ...defaultOptions, ...options };
try {
const response = await fetch(url, config);
if (!response.ok) {
throw new Error(`HTTP error! status: ${response.status}`);
}
return await response.json();
} catch (error) {
if (error.name === 'TypeError' && error.message.includes('CORS')) {
console.error('CORS error:', error);
throw new Error('Cross-origin request blocked. Check CORS configuration.');
}
throw error;
}
}
getCsrfToken() {
return document.querySelector('[name=csrfmiddlewaretoken]')?.value || '';
}
get(endpoint) {
return this.request(endpoint, { method: 'GET' });
}
post(endpoint, data) {
return this.request(endpoint, {
method: 'POST',
body: JSON.stringify(data)
});
}
put(endpoint, data) {
return this.request(endpoint, {
method: 'PUT',
body: JSON.stringify(data)
});
}
delete(endpoint) {
return this.request(endpoint, { method: 'DELETE' });
}
}
const apiClient = new ApiClient(process.env.REACT_APP_API_URL);
export default apiClient;
Error: "Access to fetch at 'http://localhost:8000/api/' from origin 'http://localhost:3000' has been blocked by CORS policy"
Solution:
# Add frontend origin to CORS_ALLOWED_ORIGINS
CORS_ALLOWED_ORIGINS = [
"http://localhost:3000",
]
Error: "CORS policy: Request header 'authorization' is not allowed"
Solution:
# Add authorization to allowed headers
CORS_ALLOWED_HEADERS = [
'authorization',
'content-type',
'x-csrftoken',
]
Error: "CORS policy: The request client is not a secure context"
Solution:
# Ensure HTTPS in production
SECURE_SSL_REDIRECT = True
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
# middleware/cors_debug.py
import logging
from django.utils.deprecation import MiddlewareMixin
logger = logging.getLogger(__name__)
class CorsDebugMiddleware(MiddlewareMixin):
"""Debug CORS issues in development."""
def process_request(self, request):
if settings.DEBUG:
origin = request.META.get('HTTP_ORIGIN')
method = request.method
logger.info(f"CORS Request: {method} from {origin}")
if method == 'OPTIONS':
logger.info("Preflight request detected")
requested_method = request.META.get('HTTP_ACCESS_CONTROL_REQUEST_METHOD')
requested_headers = request.META.get('HTTP_ACCESS_CONTROL_REQUEST_HEADERS')
logger.info(f"Requested method: {requested_method}")
logger.info(f"Requested headers: {requested_headers}")
def process_response(self, request, response):
if settings.DEBUG:
cors_headers = {
key: value for key, value in response.items()
if key.startswith('Access-Control-')
}
if cors_headers:
logger.info(f"CORS Response headers: {cors_headers}")
return response
# tests/test_cors.py
from django.test import TestCase, Client
from django.urls import reverse
class CorsTestCase(TestCase):
def setUp(self):
self.client = Client()
def test_cors_preflight_request(self):
"""Test CORS preflight request."""
response = self.client.options(
reverse('api:blog-list'),
HTTP_ORIGIN='http://localhost:3000',
HTTP_ACCESS_CONTROL_REQUEST_METHOD='POST',
HTTP_ACCESS_CONTROL_REQUEST_HEADERS='content-type'
)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response['Access-Control-Allow-Origin'],
'http://localhost:3000'
)
self.assertIn('POST', response['Access-Control-Allow-Methods'])
def test_cors_actual_request(self):
"""Test actual CORS request."""
response = self.client.get(
reverse('api:blog-list'),
HTTP_ORIGIN='http://localhost:3000'
)
self.assertEqual(response.status_code, 200)
self.assertEqual(
response['Access-Control-Allow-Origin'],
'http://localhost:3000'
)
def test_cors_blocked_origin(self):
"""Test blocked origin."""
response = self.client.get(
reverse('api:blog-list'),
HTTP_ORIGIN='http://malicious-site.com'
)
# Should not include CORS headers for blocked origins
self.assertNotIn('Access-Control-Allow-Origin', response)
# Dockerfile
FROM python:3.11-slim
# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1
WORKDIR /app
# Install dependencies
COPY requirements.txt .
RUN pip install -r requirements.txt
# Copy application
COPY . .
# Set CORS origins from environment
ENV CORS_ALLOWED_ORIGINS="https://myapp.com,https://www.myapp.com"
EXPOSE 8000
CMD ["gunicorn", "myproject.wsgi:application", "--bind", "0.0.0.0:8000"]
# k8s/deployment.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: django-api
spec:
replicas: 3
selector:
matchLabels:
app: django-api
template:
metadata:
labels:
app: django-api
spec:
containers:
- name: django-api
image: myapp/django-api:latest
ports:
- containerPort: 8000
env:
- name: CORS_ALLOWED_ORIGINS
value: "https://myapp.com,https://www.myapp.com"
- name: ENVIRONMENT
value: "production"
# nginx-ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
name: myapp-ingress
annotations:
nginx.ingress.kubernetes.io/cors-allow-origin: "https://myapp.com,https://www.myapp.com"
nginx.ingress.kubernetes.io/cors-allow-methods: "GET, POST, PUT, DELETE, OPTIONS"
nginx.ingress.kubernetes.io/cors-allow-headers: "DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range,Authorization"
nginx.ingress.kubernetes.io/cors-allow-credentials: "true"
spec:
rules:
- host: api.myapp.com
http:
paths:
- path: /
pathType: Prefix
backend:
service:
name: django-api-service
port:
number: 8000
Proper CORS configuration is crucial for secure and functional frontend-backend communication. Always follow the principle of least privilege by allowing only necessary origins, methods, and headers. Use environment-specific configurations to maintain security in production while enabling flexible development workflows. Regular testing and monitoring ensure your CORS setup remains secure and functional as your application evolves.
Using React or Vue with Django
Modern frontend frameworks like React and Vue enable building sophisticated single-page applications (SPAs) that communicate with Django backends through APIs. This chapter covers various integration patterns, from simple component embedding to full SPA architectures, including authentication, state management, and deployment strategies.
Internationalization and Localization
Building applications that serve users across different languages, cultures, and time zones requires careful planning and implementation of internationalization (i18n) and localization (l10n) features. Django provides comprehensive built-in support for creating multilingual applications that adapt to users' linguistic and cultural preferences while maintaining excellent performance and user experience.