Django Security Best Practices

Introduction

Django defaults are security-conscious—CSRF middleware, password hashing, template auto-escaping—but production security depends on your settings, deployment, and habits. This chapter consolidates checklist items: secrets, DEBUG, HTTPS, CSRF/XSS/SQL injection defenses, headers, dependency audits, and login protection—building on Settings, Forms and CSRF, and API auth.

Prerequisites

Production Settings Checklist

config/settings/production.py essentials:

SettingPurpose
DEBUG=FalseHide tracebacks and settings
SECRET_KEY from envSigning sessions/CSRF
ALLOWED_HOSTSReject wrong Host header
SECURE_SSL_REDIRECTHTTP → HTTPS
SECURE_*_COOKIECookies only over TLS
HSTSBrowser remembers HTTPS

Terminate TLS at Nginx — HTTPS and Let's Encrypt.

SECRET_KEY and Secrets

  • Generate unique key per environment
  • Never commit .env or production settings with literals
  • Rotate key if leaked (invalidates all sessions)
  • Database passwords, API keys via os.environ
bash
python -c "from django.core.management.utils import get_random_secret_key; print(get_random_secret_key())"

CSRF

  • All browser POST forms: {% csrf_token %}
  • AJAX with session auth: X-CSRFToken header
  • APIs: Token/JWT instead of disabling CSRF globally
  • @csrf_exempt: webhooks only, with signature verification

See Forms and CSRF.

XSS (Cross-Site Scripting)

Django templates escape {{ variable }} by default.

SafeRisky
{{ user_comment }}{{ user_comment|safe }} on untrusted input
mark_safe() on trusted admin HTML onlyBuilding HTML with string concat from POST

JSON APIs: DRF JSON encoder escapes strings in responses; XSS hits when frontend uses innerHTML with API data—sanitize in client or use text nodes.

Content-Security-Policy (CSP) header via middleware or Nginx—restrict script sources for high-security apps.

SQL Injection

Django ORM parameterizes queries:

python
Post.objects.filter(title=user_input)  # safe

Dangerous:

python
Post.objects.raw(f"SELECT * FROM blog_post WHERE title = '{user_input}'")  # never

Use ORM or cursor.execute(sql, [params]) with placeholders.

Clickjacking

XFrameOptionsMiddleware sets X-Frame-Options: DENY by default. Allow embedding only when intentional:

python
from django.views.decorators.clickjacking import xframe_options_exempt

User Uploads

From Static and Media:

  • Validate extension and MIME type
  • Limit file size in form and Nginx client_max_body_size
  • Store outside executable paths; serve with Content-Disposition: attachment for downloads when appropriate
  • Scan with antivirus in high-risk domains (awareness)

Authentication Hardening

AUTH_PASSWORD_VALIDATORS (default enabled):

python
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"},
]

Additional measures:

MeasureTool / approach
Login rate limitdjango-axes, Nginx limit_req, fail2ban
Admin URL obscurityNon-default path + IP allowlist
2FA for admindjango-otp
Lock inactive accountsis_active=False

Protect /admin/Django Admin security.

API Security

  • DEFAULT_PERMISSION_CLASSES: not AllowAny for writes in production
  • Object-level permissions on update/delete
  • JWT: short access TTL, refresh rotation, HTTPS only
  • Do not expose stack traces in JSON errors

API auth chapter.

CORS

  • Never CORS_ALLOW_ALL_ORIGINS = True with credentials in production
  • Whitelist exact frontend origins
  • Prefer same-origin Nginx proxy for SPAs

Dependency Scanning

bash
pip audit

Or pip-audit, GitHub Dependabot, Snyk. Pin versions in requirements.txt; update Django for security releases promptly.

Logging and Monitoring

  • Log django.request 5xx at ERROR
  • Do not log passwords, tokens, or full Authorization headers
  • Alert on error rate spikes — Error Handling and Logging
  • Sentry for exception tracking (optional)

Security Middleware Order

Keep Django defaults unless you know why:

python
MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    ...
]

SecurityMiddleware applies SECURE_* settings.

runserver and DEBUG in Production

Never in productionUse instead
manage.py runserverGunicorn + Nginx
DEBUG=TrueDEBUG=False + logging
SQLite for high trafficMySQL/PostgreSQL
Default SECRET_KEY from tutorialEnv-generated key

Pre-Deploy Security Command

bash
DJANGO_SETTINGS_MODULE=config.settings.production python manage.py check --deploy

Fixes warnings before go-live.

Common Vulnerabilities Summary

ThreatDjango defenseYour responsibility
CSRFMiddleware + tokenForms and AJAX headers
XSSTemplate escapeAvoid |safe on user data
SQL injectionORMNo raw string SQL
Session hijackHTTPS, secure cookiesTLS everywhere
Host header attackALLOWED_HOSTSSet correctly
ClickjackingX-Frame-OptionsDo not exempt blindly

Post-Chapter Checklist

  • Production settings: DEBUG=False, env SECRET_KEY, ALLOWED_HOSTS
  • HTTPS and secure cookie flags configured
  • check --deploy passes
  • CSRF on forms; API auth on writes
  • pip audit run on dependencies
  • Admin and uploads hardened

FAQ## FAQ

Django secure by default?

Mostly for CSRF, XSS in templates, SQL via ORM—but DEBUG, SECRET_KEY, HTTPS, and permissions are on you.

Security through obscurity?

Hiding /admin/ helps noise reduction only—use strong auth and network controls.

Content-Security-Policy?

Add via django-csp or Nginx—reduces XSS impact.

GDPR / privacy?

Cookie consent, data export/delete—legal/process; User model holds PII.

Penetration testing?

Run check --deploy, OWASP ZAP on staging, fix findings before prod.