Django Security Best Practices
Protecting your Django applications from common vulnerabilities
🔒 What is Django Security?
Django security involves protecting your web application from attacks like SQL injection, XSS, and CSRF. Django provides built-in security features to keep your application and users safe from common vulnerabilities.
# Django automatically protects against CSRF attacks
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def my_view(request):
return render(request, 'template.html')
Core Security Features
CSRF Protection
Prevents cross-site request forgery
<form method="post">
{% csrf_token %}
<input type="text" name="username">
</form>
SQL Injection
Use ORM to prevent SQL attacks
# Safe - uses ORM
User.objects.filter(username=name)
XSS Protection
Auto-escapes template variables
<!-- Auto-escaped -->
{{ user_input }}
Password Hashing
Secure password storage
from django.contrib.auth.hashers import make_password
pwd = make_password('secret')
🔹 SECRET_KEY Protection
Keep your SECRET_KEY private and secure. Never commit it to version control or share it publicly. Use environment variables to store sensitive configuration.
# settings.py - WRONG ❌
SECRET_KEY = 'django-insecure-hardcoded-key-123'
# settings.py - CORRECT ✅
import os
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
# Use python-decouple or django-environ
from decouple import config
SECRET_KEY = config('SECRET_KEY')
Best Practices:
- Use environment variables for SECRET_KEY
- Generate strong random keys (50+ characters)
- Never commit .env files to Git
- Rotate keys periodically
🔹 HTTPS and Secure Cookies
Always use HTTPS in production to encrypt data transmission. Configure Django to use secure cookies that are only sent over encrypted connections.
# settings.py - Production Security Settings
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
# HSTS Settings
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
🔹 User Authentication Security
Implement strong authentication practices to protect user accounts from unauthorized access and brute force attacks.
# settings.py - Password Validation
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {'min_length': 8}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# views.py - Login with rate limiting
from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required
def login_view(request):
if request.method == 'POST':
username = request.POST['username']
password = request.POST['password']
user = authenticate(request, username=username, password=password)
if user is not None:
login(request, user)
return redirect('dashboard')
return render(request, 'login.html')
🔹 SQL Injection Prevention
Django's ORM automatically protects against SQL injection. Always use ORM methods instead of raw SQL queries when possible.
# UNSAFE - Vulnerable to SQL Injection ❌
from django.db import connection
cursor = connection.cursor()
cursor.execute(f"SELECT * FROM users WHERE username = '{username}'")
# SAFE - Using Django ORM ✅
from myapp.models import User
users = User.objects.filter(username=username)
# SAFE - Using parameterized raw SQL ✅
User.objects.raw('SELECT * FROM myapp_user WHERE username = %s', [username])
ORM Security Benefits:
- Automatic query parameterization
- Protection against SQL injection
- Type validation and sanitization
- Cleaner, more maintainable code
🔹 Cross-Site Scripting (XSS) Prevention
Django templates automatically escape variables to prevent XSS attacks. Only use 'safe' filter or mark_safe() when you trust the content source.
<!-- templates/profile.html -->
<!-- SAFE - Auto-escaped ✅ -->
<p>Welcome, {{ user.username }}!</p>
<!-- UNSAFE - Marks as safe ❌ -->
<div>{{ user_bio|safe }}</div>
<!-- SAFE - Escaped HTML ✅ -->
<div>{{ user_bio }}</div>
# views.py
from django.utils.html import escape
from django.utils.safestring import mark_safe
def profile_view(request):
user_input = request.GET.get('comment', '')
# Escape untrusted input
safe_comment = escape(user_input)
# Only mark as safe if you control the content
trusted_html = mark_safe('<strong>Admin Message</strong>')
return render(request, 'profile.html', {
'comment': safe_comment,
'message': trusted_html
})
🔹 CSRF Protection
Cross-Site Request Forgery protection is enabled by default in Django. Always include csrf_token in forms that use POST, PUT, PATCH, or DELETE methods.
<!-- templates/form.html -->
<form method="post" action="/submit/">
{% csrf_token %}
<input type="text" name="title">
<button type="submit">Submit</button>
</form>
# views.py - CSRF with AJAX
from django.views.decorators.csrf import csrf_exempt, csrf_protect
# Default - CSRF protected ✅
@csrf_protect
def form_view(request):
if request.method == 'POST':
# Process form
pass
return render(request, 'form.html')
# Exempt from CSRF (use carefully) ⚠️
@csrf_exempt
def api_view(request):
# For APIs with token authentication
return JsonResponse({'status': 'ok'})
🔹 Clickjacking Protection
Prevent your site from being embedded in iframes on malicious sites. Django provides X-Frame-Options middleware for clickjacking protection.
# settings.py
X_FRAME_OPTIONS = 'DENY' # Cannot be framed at all
# or
X_FRAME_OPTIONS = 'SAMEORIGIN' # Only same domain
# views.py - Per-view control
from django.views.decorators.clickjacking import xframe_options_deny
from django.views.decorators.clickjacking import xframe_options_sameorigin
@xframe_options_deny
def sensitive_view(request):
return render(request, 'sensitive.html')
@xframe_options_sameorigin
def embed_allowed_view(request):
return render(request, 'embeddable.html')
🔹 File Upload Security
Validate and sanitize file uploads to prevent malicious files from being uploaded and executed on your server.
# models.py
from django.db import models
from django.core.validators import FileExtensionValidator
class Document(models.Model):
file = models.FileField(
upload_to='documents/',
validators=[FileExtensionValidator(
allowed_extensions=['pdf', 'doc', 'docx']
)]
)
# views.py
from django.core.exceptions import ValidationError
def upload_view(request):
if request.method == 'POST' and request.FILES.get('file'):
uploaded_file = request.FILES['file']
# Validate file size (5MB limit)
if uploaded_file.size > 5 * 1024 * 1024:
return HttpResponse('File too large', status=400)
# Validate file type
allowed_types = ['application/pdf', 'image/jpeg', 'image/png']
if uploaded_file.content_type not in allowed_types:
return HttpResponse('Invalid file type', status=400)
# Save file
doc = Document(file=uploaded_file)
doc.save()
return render(request, 'upload.html')
🔹 Security Checklist
Before Deploying to Production:
- ✅ Set DEBUG = False
- ✅ Use environment variables for secrets
- ✅ Enable HTTPS and secure cookies
- ✅ Configure ALLOWED_HOSTS properly
- ✅ Use strong SECRET_KEY
- ✅ Enable CSRF protection
- ✅ Set up password validators
- ✅ Configure HSTS headers
- ✅ Validate all user inputs
- ✅ Keep Django and dependencies updated
# settings.py - Production Checklist
DEBUG = False
ALLOWED_HOSTS = ['yourdomain.com', 'www.yourdomain.com']
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY')
# Security Settings
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
X_FRAME_OPTIONS = 'DENY'
# Run security check
# python manage.py check --deploy