security & Best Practices
Learn Django's built-in security features and best practices to protect your web applications from vulnerabilities. This is a foundational concept in Python web development that professional developers rely on daily. The explanations below are written to be beginner-friendly while covering the depth and nuance that comes from real-world Python/Django experience. Take your time with each section and practice the examples
Django security Features
Django includes many built-in security features to protect your applications from common web vulnerabilities. Understanding these features is essential for building secure applications.. This is an essential concept that every Python/Django developer must understand thoroughly. In professional development environments, getting this right can mean the difference between code that works reliably and code that breaks in production. The following sections break this down into clear, digestible pieces with practical examples you can try immediately
security Best Practices
# settings.py - security Settings
import os
from pathlib import Path
# security settings
SECRET_KEY = os.environ.get('SECRET_KEY', 'your-secret-key-here')
# HTTPS settings
SECURE_SSL_REDIRECT = True # Redirect HTTP to HTTPS
SECURE_HSTS_SECONDS = 31536000 # 1 year
SECURE_HSTS_INCLUDE_SUBDOMAINS = True
SECURE_HSTS_PRELOAD = True
# Cookie settings
SESSION_COOKIE_SECURE = True # Only send cookies over HTTPS
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_HTTPONLY = True # Prevent XSS
CSRF_COOKIE_HTTPONLY = True
# Content security Policy
CSP_DEFAULT_SRC = ("'self'",)
CSP_STYLE_SRC = ("'self'", "'unsafe-inline'")
CSP_SCRIPT_SRC = ("'self'",)
# CSRF Protection
CSRF_TRUSTED_ORIGINS = [
'https://yourdomain.com',
'https://www.yourdomain.com',
]
# XSS Protection
SECURE_BROWSER_XSS_FILTER = True
SECURE_CONTENT_TYPE_NOSNIFF = True
# Clickjacking Protection
X_FRAME_OPTIONS = 'DENY'
# SQL Injection Protection (Django ORM handles this automatically)
# Always use Django ORM instead of raw SQL when possible
# Example of secure form handling
from django.views.decorators.csrf import csrf_protect
from django.views.decorators.http import require_http_methods
@csrf_protect
@require_http_methods(["GET", "POST"])
def secure_form(request):
if request.method == 'POST':
form = MyForm(request.POST)
if form.is_valid():
# Process form data
pass
else:
form = MyForm()
return render(request, 'form.html', {'form': form})
# Rate limiting
from django.core.cache import cache
from django.http import HttpResponseTooManyRequests
def rate_limit(view_func):
def wrapper(request, *args, **kwargs):
# Get client IP
client_ip = request.META.get('REMOTE_ADDR')
# Check rate limit
cache_key = f'rate_limit_{client_ip}'
request_count = cache.get(cache_key, 0)
if request_count > 100: # 100 requests per hour
return HttpResponseTooManyRequests('Rate limit exceeded')
# Increment counter
cache.set(cache_key, request_count + 1, 3600) # 1 hour
return view_func(request, *args, **kwargs)
return wrapper
# Input validation
from django.core.validators import validate_email
from django.core.exceptions import ValidationError
def validate_user_input(data):
errors = {}
# Email validation
try:
validate_email(data.get('email', ''))
except ValidationError:
errors['email'] = 'Enter a valid email address.'
# Password strength
password = data.get('password', '')
if len(password) < 8:
errors['password'] = 'Password must be at least 8 characters long.'
elif not any(c.isupper() for c in password):
errors['password'] = 'Password must contain at least one uppercase letter.'
elif not any(c.isdigit() for c in password):
errors['password'] = 'Password must contain at least one number.'
return errors
# File upload security
import os
from django.core.files.storage import default_storage
def secure_file_upload(file):
# Check file size
if file.size > 5 * 1024 * 1024: # 5MB
raise ValidationError('File size must be under 5MB.')
# Check file extension
allowed_extensions = ['.jpg', '.jpeg', '.png', '.gif', '.pdf']
file_extension = os.path.splitext(file.name)[1].lower()
if file_extension not in allowed_extensions:
raise ValidationError('File type not allowed.')
# Generate secure filename
import uuid
secure_filename = f"{uuid.uuid4()}{file_extension}"
# Save file securely
file_path = default_storage.save(f'uploads/{secure_filename}', file)
return file_path
# SQL Injection Prevention
# ❌ Bad - Don't do this
def bad_query(request):
user_input = request.GET.get('search', '')
query = f"SELECT * FROM posts WHERE title LIKE '%{user_input}%'"
# This is vulnerable to SQL injection
# ✅ Good - Use Django ORM
def good_query(request):
user_input = request.GET.get('search', '')
posts = Post.objects.filter(title__icontains=user_input)
# Django ORM automatically escapes user input
# XSS Prevention
# Django templates automatically escape HTML
# {{ user_input }} # Safe - automatically escaped
# {{ user_input|safe }} # Dangerous - only use with trusted content
# CSRF Protection
# Django includes CSRF protection by default
# Include {% csrf_token %} in all forms
# Password hashing
from django.contrib.auth.hashers import make_password, check_password
# Hash password
hashed_password = make_password('my_password')
# Check password
is_valid = check_password('my_password', hashed_password)