Static Assets and Frontend Integration

Managing CORS

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.

Managing CORS

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.

Understanding CORS

What is CORS?

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.

When CORS is Needed

CORS configuration is required when:

  • Frontend and backend are served from different domains
  • Using different ports in development (React on :3000, Django on :8000)
  • Deploying frontend to CDN while backend runs on different domain
  • Building mobile apps that consume Django APIs
  • Creating microservices architecture with separate frontend/backend services

CORS Headers

Key CORS headers that control cross-origin access:

Response Headers (Server-side):

  • Access-Control-Allow-Origin: Specifies allowed origins
  • Access-Control-Allow-Methods: Allowed HTTP methods
  • Access-Control-Allow-Headers: Allowed request headers
  • Access-Control-Allow-Credentials: Whether credentials are allowed
  • Access-Control-Max-Age: Preflight cache duration

Request Headers (Client-side):

  • Origin: The origin making the request
  • Access-Control-Request-Method: Method for preflight requests
  • Access-Control-Request-Headers: Headers for preflight requests

Django CORS Configuration

Installing django-cors-headers

pip install django-cors-headers
# requirements.txt
django-cors-headers==4.3.1

Basic Configuration

# 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

Environment-Specific Configuration

# 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"

Dynamic CORS Configuration

# 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
]

Advanced CORS Configuration

Custom CORS Middleware

# 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

API-Specific CORS Configuration

# 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

Security Best Practices

Principle of Least Privilege

# 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

Environment-Based Security

# 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

Content Security Policy Integration

# 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"]

Frontend CORS Handling

Axios Configuration

// 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;

Fetch API with CORS

// 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;

Troubleshooting CORS Issues

Common CORS Errors

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')

CORS Debugging Tools

# 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

Testing CORS Configuration

# 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)

Production Deployment

Docker Configuration

# 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"]

Kubernetes Configuration

# 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"

Load Balancer Configuration

# 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.