Django CSRF Protection
Introduction
When building web applications, security is a critical concern. One common vulnerability that attackers exploit is called Cross-Site Request Forgery (CSRF). This is a type of attack where unauthorized commands are submitted from a user that the web application trusts.
Django provides built-in protection against CSRF attacks through its CSRF middleware and template tags. In this tutorial, we'll explore what CSRF attacks are, why they're dangerous, and how Django helps protect your web applications from them.
What is a CSRF Attack?
Imagine you're logged into your banking website in one browser tab, and you open another tab to visit a malicious website. If that malicious site contains code that submits a form to your bank's "transfer money" endpoint, your browser would include your authentication cookies with that request, potentially allowing the attacker to transfer money from your account without your permission!
That's a Cross-Site Request Forgery attack - it "forges" a request from a trusted user (you) without your knowledge or consent.
How Django Protects Against CSRF Attacks
Django prevents CSRF attacks using a simple but effective strategy:
- It generates a unique token (CSRF token) for each user session
- It requires this token to be present in any POST, PUT, PATCH or DELETE request
- If the token is missing or incorrect, the request is rejected
This ensures that only forms that originate from your own website can be successfully submitted.
Implementing CSRF Protection in Django Forms
Step 1: Ensure CSRF Middleware is Enabled
In your Django project's settings, check that the CSRF middleware is included in the MIDDLEWARE
setting:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # This is the CSRF middleware
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
This middleware is included by default when you create a new Django project, so you typically don't need to add it manually.
Step 2: Include the CSRF Token in Your Forms
For Django templates, you need to include the CSRF token in any form that uses POST, PUT, PATCH, or DELETE methods.
<form method="post" action="/submit/">
{% csrf_token %}
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">Submit</button>
</form>
The {% csrf_token %}
template tag generates a hidden input field containing the CSRF token:
<input type="hidden" name="csrfmiddlewaretoken" value="KyLKcH9aYDkIjq0O3OeCBPV2ry7AiOFFmr8ajVdQQJxDpHwWqQgfFp6J75fd2JNZ">
Step 3: Handle AJAX Requests
If you're making AJAX requests, you'll need to include the CSRF token in the request headers. You can do this by:
- Getting the CSRF token from the cookie
- Adding it to the
X-CSRFToken
header in your AJAX request
Here's an example using JavaScript:
// Function to get cookie by name
function getCookie(name) {
let cookieValue = null;
if (document.cookie && document.cookie !== '') {
const cookies = document.cookie.split(';');
for (let i = 0; i < cookies.length; i++) {
const cookie = cookies[i].trim();
if (cookie.substring(0, name.length + 1) === (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
// Getting the CSRF token
const csrftoken = getCookie('csrftoken');
// Making an AJAX request with the token
fetch('/api/submit/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify({ username: 'user1', password: 'pass123' })
})
.then(response => response.json())
.then(data => console.log(data));
Exempting Views from CSRF Protection
In some cases, you might need to exempt certain views from CSRF protection, such as APIs consumed by non-browser clients. Django provides two ways to do this:
Option 1: Using the @csrf_exempt
Decorator
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def my_api_view(request):
# Process the request
return JsonResponse({'status': 'success'})
Option 2: Using the @csrf_protect
Decorator for Specific Views
If you've disabled CSRF protection site-wide (which is not recommended), you can use the csrf_protect
decorator to enable it for specific views:
from django.views.decorators.csrf import csrf_protect
@csrf_protect
def my_secure_form_view(request):
# Process form
return render(request, 'success.html')
Real-World Example: Contact Form
Let's create a simple contact form with CSRF protection:
1. Create a form in forms.py
:
# forms.py
from django import forms
class ContactForm(forms.Form):
name = forms.CharField(max_length=100)
email = forms.EmailField()
message = forms.CharField(widget=forms.Textarea)
2. Create a view in views.py
:
# views.py
from django.shortcuts import render, redirect
from django.contrib import messages
from .forms import ContactForm
def contact_view(request):
if request.method == 'POST':
form = ContactForm(request.POST)
if form.is_valid():
# Process the form data
name = form.cleaned_data['name']
email = form.cleaned_data['email']
message = form.cleaned_data['message']
# Here you would typically save to database or send an email
messages.success(request, f"Thanks {name}, your message has been sent!")
return redirect('contact')
else:
form = ContactForm()
return render(request, 'contact.html', {'form': form})
3. Create a template in contact.html
:
<!-- contact.html -->
<!DOCTYPE html>
<html>
<head>
<title>Contact Us</title>
</head>
<body>
<h1>Contact Us</h1>
{% if messages %}
<ul class="messages">
{% for message in messages %}
<li{% if message.tags %} class="{{ message.tags }}"{% endif %}>{{ message }}</li>
{% endfor %}
</ul>
{% endif %}
<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Send Message</button>
</form>
</body>
</html>
4. Add URL pattern in urls.py
:
# urls.py
from django.urls import path
from .views import contact_view
urlpatterns = [
path('contact/', contact_view, name='contact'),
]
In this example, the {% csrf_token %}
template tag automatically adds the necessary CSRF protection to the form.
Common CSRF-Related Issues
CSRF Verification Failed Error
If you see a "CSRF verification failed. Request aborted." error, it usually means:
- The CSRF token is missing in your form
- The token has expired
- The token is invalid
Solution: Make sure that:
- Your form includes the
{% csrf_token %}
tag - The user's session hasn't expired
- You're not trying to submit a form from a different domain
AJAX Requests Failing
If your AJAX requests are failing with CSRF errors:
Solution:
- Ensure you're including the CSRF token in your request headers
- Check that you're correctly extracting the token from cookies
- Verify that your JavaScript is running after the page has loaded
Summary
Django's CSRF protection is a powerful security feature that helps protect your web applications from cross-site request forgery attacks. By including the {% csrf_token %}
template tag in your forms, you can ensure that only forms originating from your site can be successfully submitted.
Key points to remember:
- CSRF attacks trick users into submitting unauthorized requests
- Django uses unique tokens to validate that requests come from your site
- Always include the
{% csrf_token %}
tag in your forms - For AJAX requests, include the token in the
X-CSRFToken
header - Only exempt views from CSRF protection when absolutely necessary
Additional Resources
- Django Documentation on CSRF Protection
- OWASP CSRF Prevention Cheat Sheet
- Mozilla Web Security Guidelines
Exercises
- Create a simple Django form with CSRF protection and verify that the form can be submitted successfully.
- Try removing the CSRF token from a form and observe the error that occurs.
- Write JavaScript code to make an AJAX POST request with proper CSRF token handling.
- Create a view that's exempted from CSRF protection and discuss when this might be appropriate or inappropriate in a real application.
- Research and write about other security measures that should be implemented alongside CSRF protection in a production Django application.
If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)