Django security best practices: authentication, authorization, CSRF protection, SQL injection and XSS prevention, and secure deployment settings. USE WHEN hardening a Django app, reviewing auth or permissions, or auditing for CSRF, SQLi, or XSS before deploy.
Django security best practices: authentication, authorization, CSRF protection, SQL injection and XSS prevention, and secure deployment settings. USE WHEN hardening a Django app, reviewing auth or permissions, or auditing for CSRF, SQLi, or XSS before deploy.
origin
ECC
cluster
python-backend
version
1.0.0
Django Security Best Practices
Comprehensive security guidelines for Django applications to protect against common vulnerabilities.
When to Activate
Setting up Django authentication and authorization
# models.pyfrom django.db import models
from django.contrib.auth.models import Permission
classPost(models.Model):
title = models.CharField(max_length=200)
content = models.TextField()
author = models.ForeignKey(User, on_delete=models.CASCADE)
classMeta:
permissions = [
('can_publish', 'Can publish posts'),
('can_edit_others', 'Can edit posts of others'),
]
defuser_can_edit(self, user):
"""Check if user can edit this post."""returnself.author == user or user.has_perm('app.can_edit_others')
# views.pyfrom django.contrib.auth.mixins import LoginRequiredMixin, PermissionRequiredMixin
from django.views.generic import UpdateView
classPostUpdateView(LoginRequiredMixin, PermissionRequiredMixin, UpdateView):
model = Post
permission_required = 'app.can_edit_others'
raise_exception = True# Return 403 instead of redirectdefget_queryset(self):
"""Only allow users to edit their own posts."""return Post.objects.filter(author=self.request.user)
Custom Permissions
# permissions.pyfrom rest_framework import permissions
classIsOwnerOrReadOnly(permissions.BasePermission):
"""Allow only owners to edit objects."""defhas_object_permission(self, request, view, obj):
# Read permissions allowed for any requestif request.method in permissions.SAFE_METHODS:
returnTrue# Write permissions only for ownerreturn obj.author == request.user
classIsAdminOrReadOnly(permissions.BasePermission):
"""Allow admins to do anything, others read-only."""defhas_permission(self, request, view):
if request.method in permissions.SAFE_METHODS:
returnTruereturn request.user and request.user.is_staff
classIsVerifiedUser(permissions.BasePermission):
"""Allow only verified users."""defhas_permission(self, request, view):
return request.user and request.user.is_authenticated and request.user.is_verified
# GOOD: Django ORM automatically escapes parametersdefget_user(username):
return User.objects.get(username=username) # Safe# GOOD: Using parameters with raw()defsearch_users(query):
return User.objects.raw('SELECT * FROM users WHERE username = %s', [query])
# BAD: Never directly interpolate user inputdefget_user_bad(username):
return User.objects.raw(f'SELECT * FROM users WHERE username = {username}') # VULNERABLE!# GOOD: Using filter with proper escapingdefget_users_by_email(email):
return User.objects.filter(email__iexact=email) # Safe# GOOD: Using Q objects for complex queriesfrom django.db.models import Q
defsearch_users_complex(query):
return User.objects.filter(
Q(username__icontains=query) |
Q(email__icontains=query)
) # Safe
Extra Security with raw()
# If you must use raw SQL, always use parameters
User.objects.raw(
'SELECT * FROM users WHERE email = %s AND status = %s',
[user_input_email, status]
)
XSS Prevention
Template Escaping
{# Django auto-escapes variables by default - SAFE #}
{{ user_input }} {# Escaped HTML #}
{# Explicitly mark safe only for trusted content #}
{{ trusted_html|safe }} {# Not escaped #}
{# Use template filters for safe HTML #}
{{ user_input|escape }} {# Same as default #}
{{ user_input|striptags }} {# Remove all HTML tags #}
{# JavaScript escaping #}
<script>
var username = {{ username|escapejs }};
</script>
Safe String Handling
from django.utils.safestring import mark_safe
from django.utils.html import escape
# BAD: Never mark user input as safe without escapingdefrender_bad(user_input):
return mark_safe(user_input) # VULNERABLE!# GOOD: Escape first, then mark safedefrender_good(user_input):
return mark_safe(escape(user_input))
# GOOD: Use format_html for HTML with variablesfrom django.utils.html import format_html
defgreet_user(username):
return format_html('<span class="user">{}</span>', escape(username))
# settings.py - CSRF is enabled by default
CSRF_COOKIE_SECURE = True# Only send over HTTPS
CSRF_COOKIE_HTTPONLY = True# Prevent JavaScript access
CSRF_COOKIE_SAMESITE = 'Lax'# Prevent CSRF in some cases
CSRF_TRUSTED_ORIGINS = ['https://example.com'] # Trusted domains# Template usage
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Submit</button>
</form>
# AJAX requests
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
fetch('/api/endpoint/', {
method: 'POST',
headers: {
'X-CSRFToken': getCookie('csrftoken'),
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
});
Exempting Views (Use Carefully)
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt # Only use when absolutely necessary!defwebhook_view(request):
# Webhook from external servicepass
File Upload Security
File Validation
import os
from django.core.exceptions import ValidationError
defvalidate_file_extension(value):
"""Validate file extension."""
ext = os.path.splitext(value.name)[1]
valid_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf']
ifnot ext.lower() in valid_extensions:
raise ValidationError('Unsupported file extension.')
defvalidate_file_size(value):
"""Validate file size (max 5MB)."""
filesize = value.size
if filesize > 5 * 1024 * 1024:
raise ValidationError('File too large. Max size is 5MB.')
# models.pyclassDocument(models.Model):
file = models.FileField(
upload_to='documents/',
validators=[validate_file_extension, validate_file_size]
)
Secure File Storage
# settings.py
MEDIA_ROOT = '/var/www/media/'
MEDIA_URL = '/media/'# Use a separate domain for media in production
MEDIA_DOMAIN = 'https://media.example.com'# Don't serve user uploads directly# Use whitenoise or a CDN for static files# Use a separate server or S3 for media files