Common Security Mistakes in Django and How to Fix Them


Django is a powerful and secure web framework, but like any tool, its security depends on how developers use it. Many security vulnerabilities arise from misconfigurations, poor coding practices, or a lack of awareness about potential threats. Let’s go through some of the most common security mistakes in Django applications and how to fix them.
1. Keeping DEBUG=True
in Production
The Mistake
When DEBUG=True
, Django provides detailed error messages that include sensitive information like environment variables, database connection details, and application settings. This is great for debugging but dangerous in production.
The Fix
Always set DEBUG=False
in production and ensure sensitive information isn’t leaked.
DEBUG = False
ALLOWED_HOSTS = ["yourdomain.com"]
Additionally, use environment variables to manage settings securely:
import os
DEBUG = os.getenv("DJANGO_DEBUG", "False") == "True"
2. Exposing Secret Keys in Public Repositories
The Mistake
Many developers accidentally commit settings.py
to version control with their SECRET_KEY
exposed. Attackers can use this key to generate valid session cookies or even sign malicious requests.
The Fix
Move sensitive values to environment variables and keep them out of version control.
import os
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "your-default-secret-key")
Use a .env
file and load it with django-environ
:
# .env file
DJANGO_SECRET_KEY=your-very-secret-key
And load it in settings.py
:
import environ
env = environ.Env()
environ.Env.read_env()
SECRET_KEY = env("DJANGO_SECRET_KEY")
Also, add *.env
to your .gitignore
to prevent committing secrets.
3. Using Default Database Configurations
The Mistake
By default, Django does not enforce SSL/TLS encryption in database connections, leaving data vulnerable to interception.
The Fix
Use strong database configurations, especially for production:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
'NAME': env('DB_NAME'),
'USER': env('DB_USER'),
'PASSWORD': env('DB_PASSWORD'),
'HOST': env('DB_HOST'),
'PORT': env('DB_PORT'),
'OPTIONS': {
'sslmode': 'require', # Enforce SSL connection
},
}
}
For PostgreSQL, ensure that you require SSL in your database settings.
4. Not Using Secure Password Hashing
The Mistake
Using weak password hashing algorithms or storing plain-text passwords in the database.
The Fix
Django uses PBKDF2 by default, but you can switch to Argon2, which is more resistant to brute-force attacks.
PASSWORD_HASHERS = [
'django.contrib.auth.hashers.Argon2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2PasswordHasher',
'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
]
Never store passwords in plaintext and always use Django's set_password()
to hash passwords before saving.
from django.contrib.auth.models import User
user = User.objects.create(username='secureuser')
user.set_password('Secure@123') # Hashes the password
user.save()
5. Weak CSRF Protection
The Mistake
Forgetting to use CSRF tokens on forms, allowing attackers to perform Cross-Site Request Forgery (CSRF) attacks.
The Fix
Ensure that every form submission includes a CSRF token:
<form method="post">
{% csrf_token %}
<input type="text" name="username">
<button type="submit">Submit</button>
</form>
For API-based requests, ensure the CSRF middleware is properly handled by using CSRF exempt views only when necessary:
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def my_view(request):
pass # Only use if absolutely necessary
Use Django’s built-in CSRF protection headers for AJAX requests:
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
fetch('/api/endpoint/', {
method: 'POST',
headers: { 'X-CSRFToken': csrftoken },
body: JSON.stringify({ data: "value" })
});
6. Allowing SQL Injection
The Mistake
Using raw SQL queries with unescaped input allows SQL injection, where attackers can manipulate queries to extract or modify data.
# Vulnerable code
username = request.GET.get('username')
query = f"SELECT * FROM users WHERE username = '{username}'"
cursor.execute(query) # SQL Injection risk
The Fix
Always use Django's ORM instead of raw queries:
from django.db.models import Q
User.objects.filter(Q(username=username))
If you must use raw SQL, use parameterized queries:
cursor.execute("SELECT * FROM users WHERE username = %s", [username])
7. Not Using HTTPS
The Mistake
Serving your site over HTTP exposes sensitive user data, including login credentials and session cookies.
The Fix
Force HTTPS by setting SECURE_SSL_REDIRECT
:
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
Redirect all HTTP traffic to HTTPS in Nginx:
server {
listen 80;
server_name yourdomain.com;
return 301 https://$host$request_uri;
}
Use Let's Encrypt
or other SSL providers to enable HTTPS.
8. Using Default Django Admin URL
The Mistake
Leaving the Django Admin panel exposed at /admin/
makes it an easy target for brute-force attacks.
The Fix
Change the default admin URL:
urlpatterns = [
path("secureadmin/", admin.site.urls),
]
Additionally, restrict access using IP whitelisting:
MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django_ip_restrict.middleware.IPRestrictMiddleware",
]
IP_RESTRICTOR_ALLOW_LIST = ['YOUR_IP_ADDRESS']
Use Django Admin Honeypot to mislead attackers:
pip install django-admin-honeypot
And add to your urls.py
:
import honeypot
urlpatterns = [
path("admin/", include("honeypot.urls", namespace="honeypot")),
]
9. Poor Session Management
The Mistake
Django’s default session storage allows session hijacking if not properly secured.
The Fix
- Set session cookies to be HTTP-only and secure:
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True
- Use database-backed sessions instead of storing them in cookies:
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
- Enable session expiration:
SESSION_COOKIE_AGE = 3600 # 1 hour
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
Final Thoughts
Django provides excellent security features out-of-the-box, but it's up to developers to configure and use them properly. By following these best practices, you can significantly reduce security risks and build a safer Django application.
Security isn’t a one-time effort—it’s a continuous process. Regularly audit your code, stay updated with Django security releases, and use tools like django-secure
, bandit
, and django-security-checker
to automate security checks.
For more such tips, visit me at AhmadWKhan.com
Subscribe to my newsletter
Read articles from Ahmad W Khan directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
