Django Security Best Practices
Implement comprehensive security measures including CSRF, XSS prevention, and SQL injection protection.
Overview
Django's security features provide substantial protection against common web vulnerabilities, but they require proper implementation and configuration. Understanding each layer of Django's security middleware and when to customize it ensures robust application security. CSRF protection in Django works through the {% csrf_token %} template tag and the csrf_exempt decorator sparingly. All POST forms must include csrf_token, and AJAX requests should include the X-CSRFToken header. The CsrfViewMiddleware processes every POST request, rejecting those without valid tokens. Custom views handling AJAX should use ensure_csrf_cookie when explicit token passing is impractical. XSS prevention relies on Django's auto-escaping in templates, but this protection disappears with |safe filters or mark_safe(). These should be used only when content is definitively sanitized. ContentSecurityPolicy headers provide additional defense by controlling what resources pages can load, preventing inline scripts and external resource hijacking. SQL injection is prevented by Django's ORM parameterization, but raw queries with extra() or raw() require extreme care. Never interpolate user input directly into SQL strings. The parameterized query pattern should be automatic—every database query should use ? or %s placeholders with separate parameter values. Password hashing uses PBKDF2 by default with 720000 iterations, meeting modern security standards. Custom hashers should only be used for migration compatibility, never for new implementations. Django's authentication views and forms already implement secure patterns—replacing them without equivalent security review creates vulnerabilities.
Code Example
from django.http import HttpResponse, HttpResponseForbidden
from django.views.decorators.http import require_http_methods
from django.views.decorators.csrf import csrf_protect, csrf_exempt
from django.shortcuts import render
from django.contrib.auth.decorators import login_required, permission_required
from django.middleware.security import SecurityMiddleware
from django.conf import settings
import hashlib
import secrets
# Secure file download with authentication
@login_required
@require_http_methods(["GET"])
def download_file(request, file_id):
if not request.user.has_perm('documents.can_download'):
return HttpResponseForbidden("Permission denied")
file_obj = get_object_or_404(Document, id=file_id, owner=request.user)
response = HttpResponse(file_obj.content, content_type='application/octet-stream')
response['Content-Disposition'] = f'attachment; filename="{file_obj.name}"'
response['X-Content-Type-Options'] = 'nosniff'
response['Cache-Control'] = 'no-cache, no-store, must-revalidate'
return response
# Secure API endpoint with CSRF and rate limiting
@csrf_protect
@require_http_methods(["POST"])
def api_submit(request):
if not request.headers.get('X-Requested-With') == 'XMLHttpRequest':
return HttpResponseForbidden("AJAX required")
data = json.loads(request.body)
if not data.get('captcha_token'):
return JsonResponse({'error': 'CAPTCHA required'}, status=400)
# Rate limiting using Django cache
client_ip = request.META.get('REMOTE_ADDR')
rate_key = f'rate_limit:{client_ip}'
rate_count = cache.get(rate_key, 0)
if rate_count >= settings.RATE_LIMIT_PER_MINUTE:
return JsonResponse(
{'error': 'Rate limit exceeded'},
status=429,
headers={'Retry-After': '60'}
)
cache.set(rate_key, rate_count + 1, 60)
# Process with sanitized input
serializer = SubmissionSerializer(data={
'email': data.get('email'),
'message': sanitize_html(data.get('message')),
})
if serializer.is_valid():
serializer.save()
return JsonResponse({'status': 'success'})
return JsonResponse({'errors': serializer.errors}, status=400)
# Secure password reset with token
@csrf_protect
def password_reset_request(request):
if request.method == 'POST':
email = request.POST.get('email')
try:
user = User.objects.get(email=email)
token = generate_secure_token()
PasswordResetToken.objects.create(user=user, token=token)
send_password_reset_email(user.email, token)
except User.DoesNotExist:
pass # Don't reveal email existence
return JsonResponse({'status': 'If email exists, reset link was sent'})
# Content Security Policy
class CSPMiddleware(SecurityMiddleware):
async def __call__(self, scope, receive, send):
response = await self.get_response(scope, receive, send)
response['Content-Security-Policy'] = (
"default-src 'self'; "
"script-src 'self' 'nonce-{nonce}'; "
"style-src 'self' 'unsafe-inline'; "
"img-src 'self' data: https:; "
"font-src 'self'; "
"frame-ancestors 'none';"
)
return responseMore Django Rules
Django REST Framework Serializer Validation
Implement multi-layer validation in DRF serializers for robust API input handling and security.
from rest_framework import serializers
from django.contrib.auth import get_user_model
from django.utils import timezone
from .models import Project, T...Django QuerySet Optimization
Use select_related, prefetch_related, and only() to minimize database queries.
from django.db.models import Prefetch, Count, Q
from django.shortcuts import render
from .models import Author, Book, Publisher
# Problematic: N+1 qu...Django REST Framework Permissions
Implement granular permission systems with DRF permission classes and custom permission logic.
from rest_framework import permissions
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework.gener...