Skip to main content

Django CSRF Middleware

Introduction

Cross-Site Request Forgery (CSRF) is a type of web security vulnerability that allows attackers to trick users into performing unwanted actions on websites where they're authenticated. Django's built-in CSRF middleware provides protection against these attacks by ensuring that requests to your site come from legitimate sources.

In this tutorial, you'll learn:

  • What CSRF attacks are and why they're dangerous
  • How Django's CSRF middleware works
  • How to implement CSRF protection in your Django applications
  • Common issues and troubleshooting techniques

What is a CSRF Attack?

Before diving into the middleware, let's understand what we're protecting against:

A CSRF attack occurs when a malicious website tricks a user's browser into making an unwanted request to another site where the user is already logged in. For example:

  1. You log into your banking website and remain authenticated
  2. You visit a malicious website that contains hidden code
  3. This code automatically sends a request to your bank to transfer money
  4. Your browser includes your authentication cookies with the request
  5. The bank processes the transfer, thinking it was you

Without CSRF protection, your Django application would be vulnerable to similar attacks.

How Django CSRF Middleware Works

Django's CSRF protection works by adding a token to each form submitted via POST, PUT, PATCH, or DELETE methods. This token is:

  1. Generated when rendering the form
  2. Verified when the form is submitted
  3. Invalid if it doesn't match what the server expects

The django.middleware.csrf.CsrfViewMiddleware is enabled by default in Django's settings and handles this process automatically.

Behind the Scenes

When the middleware is active, Django:

  1. Generates a random, secret CSRF token for each user session
  2. Includes this token in forms as a hidden field named csrfmiddlewaretoken
  3. Also sets a cookie called csrftoken containing the same value
  4. On form submission, compares the token from the request with the one in the cookie
  5. Rejects the request if the tokens don't match

Implementing CSRF Protection

Basic Usage in Templates

When using Django's template system, you can add the CSRF token to your forms with the {% csrf_token %} template tag:

html
<form method="POST" action="/submit/">
{% csrf_token %}
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">Submit</button>
</form>

This will render as:

html
<form method="POST" action="/submit/">
<input type="hidden" name="csrfmiddlewaretoken" value="KbyUmhTLMpYj7CD2di7JKP1P3qmLlkPt">
<input type="text" name="username">
<input type="password" name="password">
<button type="submit">Submit</button>
</form>

CSRF in AJAX Requests

For AJAX requests, you need to include the CSRF token in your headers:

javascript
// Get the CSRF token from the cookie
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;
}

// Example AJAX request with CSRF token
const csrftoken = getCookie('csrftoken');

fetch('/api/submit/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify({ username: 'user', password: 'pass' })
})
.then(response => response.json())
.then(data => console.log(data));

Exempting Views from CSRF Protection

Sometimes, you might need to exempt certain views from CSRF protection (like for public APIs). Django provides a decorator for this purpose:

python
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def my_api_view(request):
# This view won't require a CSRF token
if request.method == 'POST':
# Process the POST request
return JsonResponse({"status": "success"})
return JsonResponse({"status": "error"})

Be careful with this decorator! Only use it when absolutely necessary, as it removes an important security feature.

CSRF in Class-Based Views

For class-based views, you can use the csrf_exempt decorator on the dispatch method:

python
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
from django.views import View

class MyApiView(View):
@method_decorator(csrf_exempt)
def dispatch(self, *args, **kwargs):
return super().dispatch(*args, **kwargs)

def post(self, request):
# Process the POST request
return JsonResponse({"status": "success"})

Real-World Example: Secure Contact Form

Let's create a simple contact form with proper CSRF protection:

Step 1: Create the form in forms.py

python
# 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)

Step 2: Create a view in views.py

python
# 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':
# CSRF validation happens automatically
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']

# Save to database or send email
# ...

messages.success(request, "Thank you for your message!")
return redirect('contact')
else:
form = ContactForm()

return render(request, 'contact.html', {'form': form})

Step 3: Create the HTML template

html
<!-- templates/contact.html -->
{% extends "base.html" %}

{% block content %}
<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>
{% endblock %}

Common Issues and Solutions

1. CSRF Verification Failed Error

If you see "CSRF verification failed. Request aborted," it usually means:

  • The CSRF token is missing from your form
  • The token has expired
  • The token is invalid

Solution: Make sure every form includes the {% csrf_token %} tag, and that cookies are being properly set.

2. CSRF in Subdomains

Django's CSRF protection can be tricky with subdomains. By default, the CSRF cookie is not shared between subdomains.

To allow CSRF tokens to work across subdomains, set:

python
# settings.py
CSRF_COOKIE_DOMAIN = '.example.com' # Note the leading dot

3. CSRF in Testing

When testing your Django application, you might need to include CSRF tokens in your test client requests:

python
from django.test import Client, TestCase

class MyTest(TestCase):
def test_my_form(self):
client = Client(enforce_csrf_checks=True)

# Get the CSRF token from a GET request
response = client.get('/contact/')
csrftoken = response.cookies['csrftoken'].value

# Include the token in the POST request
response = client.post('/contact/',
{'name': 'Test User', 'email': '[email protected]', 'message': 'Hello'},
HTTP_X_CSRFTOKEN=csrftoken)

self.assertEqual(response.status_code, 302) # Expect a redirect

Under the Hood: Django's CSRF Middleware Implementation

For those interested in how Django implements CSRF protection internally, here's a simplified overview of the middleware:

python
# This is a simplified representation of Django's CSRF middleware, not actual Django code
class CsrfViewMiddleware:
def __init__(self, get_response):
self.get_response = get_response

def __call__(self, request):
# Skip CSRF checks for certain methods
if request.method not in ('GET', 'HEAD', 'OPTIONS', 'TRACE'):
if getattr(request, 'csrf_processing_done', False):
return self.get_response(request)

# Check if the view is exempt from CSRF protection
if hasattr(request, '_dont_enforce_csrf_checks'):
return self.get_response(request)

# Check the CSRF token in the request against the one in the cookie
csrf_token = request.META.get('HTTP_X_CSRFTOKEN') or request.POST.get('csrfmiddlewaretoken')

if not csrf_token or csrf_token != request.COOKIES.get('csrftoken'):
return django.http.HttpResponseForbidden('CSRF verification failed')

response = self.get_response(request)
return response

Summary

Django's CSRF middleware is a critical security component that protects your application from cross-site request forgery attacks. In this tutorial, we've learned:

  • How CSRF attacks work and why they're dangerous
  • How Django's CSRF protection mechanism works
  • How to properly include CSRF tokens in forms and AJAX requests
  • How to handle common CSRF-related issues
  • How to exempt views from CSRF protection when necessary

By properly implementing CSRF protection, you're taking an important step in securing your Django application against one of the most common web vulnerabilities.

Additional Resources

To deepen your understanding of CSRF protection in Django:

  1. Django's Official CSRF Documentation
  2. OWASP CSRF Prevention Cheat Sheet
  3. Django Security Best Practices

Practice Exercises

  1. Create a Django form with proper CSRF protection and test submitting it.
  2. Build a simple AJAX POST request that includes the CSRF token.
  3. Write a test case that verifies CSRF protection is working correctly in your application.
  4. Create a view that needs to be exempted from CSRF protection and explain why the exemption is necessary.
  5. Analyze your existing Django project for proper CSRF implementation and fix any issues you find.


If you spot any mistakes on this website, please let me know at [email protected]. I’d greatly appreciate your feedback! :)