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

🧠 Test Your Knowledge

What should you include in every POST form to prevent CSRF attacks?