Skip to main content

Django CSRF Protection

Introduction

Cross-Site Request Forgery (CSRF) is a common web security vulnerability where unauthorized commands are submitted from a user that a website trusts. Django includes built-in CSRF protection mechanisms to prevent these attacks. In this tutorial, we'll explore what CSRF attacks are, why they're dangerous, and how Django's protection system works.

What is a CSRF Attack?

Imagine you're logged into your banking website in one browser tab. In another tab, you visit a malicious website that contains code to submit a form to your banking site, potentially transferring money without your knowledge. Since your browser automatically includes authentication cookies with requests to the banking site, the banking site would process this unauthorized request as legitimate.

CSRF protection prevents these attacks by ensuring that requests to your Django application must include a secret token that malicious sites cannot access.

How Django's CSRF Protection Works

Django's CSRF protection consists of:

  1. A CSRF cookie containing a token that is set in the user's browser
  2. A hidden form field that must include this token with POST requests
  3. Middleware that checks if the token in the form matches the cookie

When these don't match or are missing, Django refuses to process the request.

Setting Up CSRF Protection in Django

Step 1: Ensure Middleware is Enabled

Django includes CSRF protection by default. The django.middleware.csrf.CsrfViewMiddleware should be in your MIDDLEWARE setting:

python
MIDDLEWARE = [
# ...
'django.middleware.csrf.CsrfViewMiddleware',
# ...
]

Step 2: Adding the CSRF Token to Forms

For HTML templates, you need to include the CSRF token in your forms using 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">Login</button>
</form>

This renders 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">Login</button>
</form>

Step 3: AJAX Requests

For AJAX POST requests, you need to include the CSRF token in the HTTP headers. Django helps with this by providing a JavaScript function:

html
<script>
// 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;
}

const csrftoken = getCookie('csrftoken');

// Use the token in a fetch request
fetch('/api/submit/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': csrftoken
},
body: JSON.stringify({ key: 'value' })
});
</script>

CSRF Exemptions

Sometimes you may need to exempt a view from CSRF protection (e.g., for APIs used by third-party services). Django provides the @csrf_exempt decorator:

python
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def my_view(request):
# This view won't require CSRF verification
return HttpResponse("CSRF protection disabled")

⚠️ Warning: Only use csrf_exempt when absolutely necessary, as it makes the view vulnerable to CSRF attacks.

CSRF with Class-Based Views

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

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

@method_decorator(csrf_exempt, name='dispatch')
class MyView(View):
def post(self, request):
# Process the POST request
return HttpResponse("Processed")

Testing with CSRF Protection

When testing Django views with CSRF protection enabled, your test client needs to handle the CSRF tokens. The Django test client does this automatically when you use client.post():

python
from django.test import TestCase, Client

class MyTestCase(TestCase):
def test_post_with_csrf(self):
client = Client(enforce_csrf_checks=True)
response = client.post('/my-url/', {'data': 'value'})
# Django test client automatically handles CSRF tokens

Real-World Example: Secure Form Handling

Let's see a complete example of a secure form that uses CSRF protection:

models.py:

python
from django.db import models

class Contact(models.Model):
name = models.CharField(max_length=100)
email = models.EmailField()
message = models.TextField()
created_at = models.DateTimeField(auto_now_add=True)

def __str__(self):
return f"Message from {self.name}"

forms.py:

python
from django import forms
from .models import Contact

class ContactForm(forms.ModelForm):
class Meta:
model = Contact
fields = ['name', 'email', 'message']

views.py:

python
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():
form.save()
messages.success(request, "Your message has been sent!")
return redirect('contact')
else:
form = ContactForm()

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

contact.html:

html
{% extends 'base.html' %}

{% block content %}
<h1>Contact Us</h1>

{% if messages %}
{% for message in messages %}
<div class="alert {% if message.tags %}alert-{{ message.tags }}{% endif %}">
{{ message }}
</div>
{% endfor %}
{% endif %}

<form method="post">
{% csrf_token %}
{{ form.as_p }}
<button type="submit">Send Message</button>
</form>
{% endblock %}

The {% csrf_token %} in the template automatically adds the CSRF token to the form, protecting it from CSRF attacks.

Common CSRF Issues and Solutions

Issue 1: "CSRF verification failed"

If you encounter this error, check:

  • Is the {% csrf_token %} tag included in your form?
  • Has the CSRF cookie expired?
  • Are you using HTTPS? CSRF cookies are often set with the secure flag.

Issue 2: API requests failing with 403 Forbidden

For API requests:

  • Ensure you're sending the CSRF token in the header as shown in the AJAX example
  • Consider whether the endpoint truly needs CSRF protection or if another security mechanism (like JWT) is more appropriate

Issue 3: Form not working in an iframe

Django's CSRF protection prevents forms from being submitted in iframes on different domains. This is a security feature, but if needed, you can adjust the X_FRAME_OPTIONS setting:

python
# settings.py
X_FRAME_OPTIONS = 'SAMEORIGIN' # Default, allows frames on same domain

Summary

CSRF protection is a critical security feature in Django that helps prevent unauthorized commands from being executed in a user's session. By including CSRF tokens in forms and AJAX requests, Django verifies that requests are legitimate before processing them.

Key points to remember:

  • Always include {% csrf_token %} in your POST forms
  • For AJAX requests, include the CSRF token in the X-CSRFToken header
  • Only use @csrf_exempt when absolutely necessary
  • Django's test client automatically handles CSRF tokens

Additional Resources

Practice Exercises

  1. Create a Django form that uses CSRF protection to submit user data securely
  2. Implement an AJAX POST request that includes the proper CSRF token
  3. Write a test case that verifies your form is protected against CSRF attacks
  4. Try creating a form without CSRF protection and observe the error in development

By understanding and implementing CSRF protection, you're taking an important step toward building secure Django applications.



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