Django Security Best Practices
Introduction
Security is a critical aspect of web development that cannot be overlooked. Django, as a high-level Python web framework, comes with several built-in security features, but implementing proper security practices is still the responsibility of developers. This guide aims to provide you with comprehensive knowledge about Django security best practices that will help you build secure web applications.
Django's philosophy includes security by default, but understanding these security mechanisms and how to properly configure them is essential for maintaining a secure application. Whether you're building a small personal project or an enterprise-level application, these security practices should be part of your development workflow.
Understanding Django's Security Features
Django provides several built-in security features that protect your application against common vulnerabilities:
- Cross-Site Scripting (XSS) Protection: Django's template system automatically escapes variables
- Cross-Site Request Forgery (CSRF) Protection: Middleware that validates requests
- SQL Injection Protection: ORM that handles database queries securely
- Clickjacking Protection: X-Frame-Options middleware
- Host Header Validation: Protection against HTTP Host header attacks
- SSL/HTTPS Support: For encrypted connections
Let's explore these features and best practices in detail.
Keep Django Updated
Why Updates Matter
Security vulnerabilities are discovered regularly, and Django's development team releases security patches to address these issues.
Best Practice
Always use the latest stable version of Django or at minimum, ensure you're using a supported version that receives security updates.
# Check your Django version
import django
print(django.get_version())
Output:
4.2.1
How to Update Django
Update Django using pip:
pip install --upgrade django
Properly Configure Settings.py
Your settings.py
file contains crucial security settings. Let's review the most important ones:
Secret Key
The SECRET_KEY
setting is used for cryptographic signing. If compromised, attackers could potentially forge session data or execute arbitrary code.
# Bad practice - hardcoded secret key
SECRET_KEY = 'my_secret_key_123'
# Good practice - load from environment variable
import os
from django.core.management.utils import get_random_secret_key
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', get_random_secret_key())
Debug Mode
Never enable debug mode in production environments.
# Development settings
DEBUG = True # Only for development!
# Production settings
DEBUG = False # Always in production
Allowed Hosts
Define which hosts your Django site can serve:
# Development
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
# Production - be specific about domains
ALLOWED_HOSTS = ['example.com', 'www.example.com']
# Dangerous! Don't do this in production:
ALLOWED_HOSTS = ['*'] # Allows any host
HTTPS Settings
Enforce HTTPS in production:
# Production settings
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
Protect Against Common Attacks
Cross-Site Scripting (XSS)
Django's template system automatically escapes variables to prevent XSS attacks. However, you should be careful when explicitly marking content as safe.
# Template example - safe by default
{{ user_comment }} # Output is automatically escaped
# Dangerous - only do this when absolutely necessary and content is trusted
{{ user_comment|safe }} # Output is not escaped
In your HTML templates:
<!-- Safe: -->
<div>{{ user_input }}</div>
<!-- Potentially dangerous: -->
<div>{% autoescape off %}{{ user_input }}{% endautoescape %}</div>
Cross-Site Request Forgery (CSRF)
Django provides CSRF protection through middleware. Always use it for forms that modify data:
# In settings.py
MIDDLEWARE = [
# ...
'django.middleware.csrf.CsrfViewMiddleware',
# ...
]
In forms:
<form method="post">
{% csrf_token %}
<!-- form fields -->
<button type="submit">Submit</button>
</form>
For AJAX requests, include the CSRF token:
fetch(url, {
method: 'POST',
headers: {
'X-CSRFToken': document.querySelector('[name=csrfmiddlewaretoken]').value,
'Content-Type': 'application/json',
},
body: JSON.stringify(data)
})
SQL Injection
Use Django's ORM and avoid raw SQL queries:
# Safe - using the ORM
users = User.objects.filter(username=username)
# Dangerous - raw SQL with string formatting
from django.db import connection
cursor = connection.cursor()
cursor.execute("SELECT * FROM auth_user WHERE username = '%s'" % username) # Vulnerable to SQL injection
# Safe - raw SQL with parameters
cursor.execute("SELECT * FROM auth_user WHERE username = %s", [username])
Clickjacking Protection
Django provides the X-Frame-Options
middleware to prevent your site from being displayed in frames or iframes.
# In settings.py
MIDDLEWARE = [
# ...
'django.middleware.clickjacking.XFrameOptionsMiddleware',
# ...
]
# Default setting
X_FRAME_OPTIONS = 'SAMEORIGIN'
# To completely prevent framing
X_FRAME_OPTIONS = 'DENY'
Secure File Uploads
File uploads can be a security risk if not handled properly:
Validate File Types
def validate_file(file):
# Check file size
if file.size > 5 * 1024 * 1024: # 5 MB limit
raise ValidationError("File too large")
# Check file extension
valid_extensions = ['.pdf', '.doc', '.docx']
ext = os.path.splitext(file.name)[1]
if ext.lower() not in valid_extensions:
raise ValidationError("Unsupported file extension")
# In your model
from django.core.validators import FileExtensionValidator
class Document(models.Model):
file = models.FileField(
upload_to='documents/',
validators=[
FileExtensionValidator(allowed_extensions=['pdf', 'doc', 'docx']),
validate_file
]
)
Store Uploaded Files Securely
Store uploaded files outside the document root and use Django's FileField
or ImageField
.
# In settings.py
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
User Authentication Best Practices
Password Validation
Django provides built-in password validators:
# In settings.py
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
'OPTIONS': {
'min_length': 10,
}
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
Session Security
Configure session settings for better security:
# In settings.py
SESSION_COOKIE_AGE = 1209600 # 2 weeks in seconds
SESSION_COOKIE_SECURE = True # Only send cookies over HTTPS
SESSION_COOKIE_HTTPONLY = True # Prevents JavaScript access to session cookie
Implement Two-Factor Authentication
Consider using packages like django-two-factor-auth
for enhanced security:
pip install django-two-factor-auth
Add to installed apps:
# In settings.py
INSTALLED_APPS = [
# ...
'django_otp',
'django_otp.plugins.otp_totp',
'django_otp.plugins.otp_static',
'two_factor',
# ...
]
MIDDLEWARE = [
# ...
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django_otp.middleware.OTPMiddleware',
# ...
]
Content Security Policy (CSP)
Implement CSP to prevent XSS and data injection attacks:
# Install django-csp
pip install django-csp
Configure in settings:
# In settings.py
MIDDLEWARE = [
# ...
'csp.middleware.CSPMiddleware',
# ...
]
# Basic CSP configuration
CSP_DEFAULT_SRC = ("'self'",)
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'", 'fonts.googleapis.com')
CSP_SCRIPT_SRC = ("'self'",)
CSP_FONT_SRC = ("'self'", 'fonts.gstatic.com')
CSP_IMG_SRC = ("'self'", 'data:')
Security Headers
Implement additional security headers using Django middleware:
# Custom middleware for security headers
class SecurityHeadersMiddleware:
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
response = self.get_response(request)
response['X-Content-Type-Options'] = 'nosniff'
response['Referrer-Policy'] = 'strict-origin-when-cross-origin'
response['Permissions-Policy'] = 'geolocation=(), microphone=()'
return response
# Add to middleware in settings.py
MIDDLEWARE = [
# ...
'yourapp.middleware.SecurityHeadersMiddleware',
# ...
]