# 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.

```python
DEBUG = False
ALLOWED_HOSTS = ["yourdomain.com"]
```

Additionally, use environment variables to manage settings securely:

```python
import os
DEBUG = os.getenv("DJANGO_DEBUG", "False") == "True"
```

---

## **2\. Exposing Secret Keys in Public Repositories**

### **The Mistake**

Many developers accidentally commit [`settings.py`](http://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.

```python
import os
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "your-default-secret-key")
```

Use a `.env` file and load it with `django-environ`:

```python
# .env file
DJANGO_SECRET_KEY=your-very-secret-key
```

And load it in [`settings.py`](http://settings.py):

```python
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:

```python
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.

```python
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.

```python
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:

```xml
<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**:

```python
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:

```javascript
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.

```python
# 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:

```python
from django.db.models import Q
User.objects.filter(Q(username=username))
```

If you must use raw SQL, use parameterized queries:

```python
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`:

```python
SECURE_SSL_REDIRECT = True
SESSION_COOKIE_SECURE = True
CSRF_COOKIE_SECURE = True
```

Redirect all HTTP traffic to HTTPS in Nginx:

```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:

```python
urlpatterns = [
    path("secureadmin/", admin.site.urls),
]
```

Additionally, restrict access using IP whitelisting:

```python
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:

```bash
pip install django-admin-honeypot
```

And add to your [`urls.py`](http://urls.py):

```python
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**:
    

```python
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True
```

* Use **database-backed sessions** instead of storing them in cookies:
    

```python
SESSION_ENGINE = 'django.contrib.sessions.backends.db'
```

* Enable session expiration:
    

```python
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](https://AhmadWKhan.com)
