Django CSRF Verification Failed: Complete Troubleshooting Guide
Comments
Sign in to join the conversation
Sign in to join the conversation
If you're building web applications with Django, you've likely encountered this error:
Forbidden (403)
CSRF verification failed. Request aborted.
This error can be confusing, especially when you're sure your form is set up correctly. Let's break down what CSRF protection is, why Django enforces it, and how to fix common issues.
CSRF (Cross-Site Request Forgery) is a type of web attack where a malicious website tricks your browser into making unwanted requests to another website where you're logged in.
Example Attack Scenario:
bank.comevil.comevil.com has a hidden form that submits to bank.com/transferbank.com cookiesDjango's CSRF protection prevents this by requiring a secret token that only your legitimate forms would have.
Django protects against CSRF attacks using a token-based system:
{% csrf_token %} in TemplateThe most common issue - forgetting to include the CSRF token in your form.
<!-- WRONG: Missing CSRF token -->
<form method="post" action="/submit/">
<input type="text" name="username">
<button type="submit">Submit</button>
</form>
Fix: Add {% csrf_token %}
<!-- CORRECT -->
<form method="post" action="/submit/">
{% csrf_token %}
<input type="text" name="username">
<button type="submit">Submit</button>
</form>
This generates a hidden field like:
<input type="hidden" name="csrfmiddlewaretoken" value="abc123...">
Making POST requests via JavaScript without including the CSRF token.
// WRONG: AJAX POST without CSRF token
fetch('/api/submit/', {
method: 'POST',
body: JSON.stringify(\{data: 'value'\})
})
.then(response => response.json());
// 403 Forbidden - CSRF verification failed
Fix Option 1: Include token in request headers
// CORRECT: Get CSRF token from 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();
cookie name name
Fix Option 2: Use jQuery (automatically handles CSRF)
// CORRECT: jQuery with CSRF setup
function getCookie(name) {
// ... (same as above)
}
const csrftoken = getCookie('csrftoken');
$.ajaxSetup(\{
beforeSend: function(xhr, settings) \{
if (!(/^http:.*/.test(settings.url) || /^https:.*/.testsettings \
Fix Option 3: Django's csrf.js (modern approach)
<!-- In your template -->
<script>
const csrftoken = document.querySelector('[name=csrfmiddlewaretoken]').value;
</script>
<script>
// Use in fetch
fetch('/api/submit/', \{
method: 'POST',
headers: \{
'X-CSRFToken': csrftoken
CSRF middleware is missing or disabled in settings.
# settings.py
# WRONG: CSRF middleware commented out or missing
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
# 'django.middleware.csrf.CsrfViewMiddleware', # MISSING!
'django.contrib.auth.middleware.AuthenticationMiddleware',
...
]
Fix: Ensure CSRF middleware is enabled
# CORRECT
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # Must be here!
'django.contrib.auth.middleware.AuthenticationMiddleware',
...
]
CSRF tokens are domain-specific. Issues arise with:
# Problem: Token generated on http://example.com
# but form submitted from https://example.com
Fix: Configure trusted origins for HTTPS
# settings.py
# For Django 4.0+
CSRF_TRUSTED_ORIGINS = [
'https://yourdomain.com',
'https://*.yourdomain.com', # Include subdomains
]
# For local development with different ports
if DEBUG:
CSRF_TRUSTED_ORIGINS += [
'http://localhost:3000',
'http://127.0.0.1:3000',
]
Using POST but configured for GET, or vice versa.
# views.py
from django.views.decorators.http import require_http_methods
@require_http_methods(["GET"]) # Only allows GET
def my_view(request):
# But template has POST form
return render(request, 'form.html')
Fix: Ensure method consistency
# CORRECT
@require_http_methods(["GET", "POST"])
def my_view(request):
if request.method == "POST":
# Handle POST with CSRF
form = MyForm(request.POST)
if form.is_valid():
form.save()
else:
# Handle GET
form = MyForm()
return render(request, 'form.html', \{'form': form\})
Exempting views from CSRF protection unnecessarily.
from django.views.decorators.csrf import csrf_exempt
# WRONG: Using csrf_exempt on a form view
@csrf_exempt
def submit_form(request):
# This disables CSRF protection!
return render(request, 'form.html')
When to use @csrf_exempt:
Fix: Only exempt when absolutely necessary
# CORRECT: Only exempt API endpoints with proper auth
from rest_framework.decorators import api_view, authentication_classes
from rest_framework.authentication import TokenAuthentication
@api_view(['POST'])
@authentication_classes([TokenAuthentication])
def api_endpoint(request):
# Token auth provides security; CSRF not needed
return JsonResponse(\{'status': 'ok'\})
Modern browsers enforce SameSite cookie policies that can interfere with CSRF tokens.
# settings.py
# Problem: Cookies not sent with cross-origin requests
CSRF_COOKIE_SAMESITE = 'Strict' # Too restrictive
Fix: Adjust SameSite settings
# CORRECT: Balance security and functionality
CSRF_COOKIE_SAMESITE = 'Lax' # Default, works for most cases
# For cross-origin scenarios (be careful!)
CSRF_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SECURE = True # Required with SameSite=None
DRF handles CSRF differently depending on authentication.
# views.py
from rest_framework.views import APIView
from rest_framework.response import Response
class MyAPIView(APIView):
# If using Session Authentication, CSRF is enforced
authentication_classes = [SessionAuthentication]
def post(self, request):
return Response(\{'status': 'ok'\})
Fix: Include CSRF token or use token auth
# Option 1: Use Token Authentication (no CSRF needed)
from rest_framework.authentication import TokenAuthentication
class MyAPIView(APIView):
authentication_classes = [TokenAuthentication]
# CSRF not required with token auth
# Option 2: Include CSRF token in headers (if using SessionAuth)
# See AJAX examples above
Tests fail because CSRF checks are enabled.
from django.test import TestCase, Client
class MyViewTests(TestCase):
def test_submit_form(self):
client = Client()
response = client.post('/submit/', \{'data': 'value'\})
# This might fail with CSRF error
Fix: Django test client handles CSRF automatically
# CORRECT: Test client automatically includes CSRF token
from django.test import TestCase
class MyViewTests(TestCase):
def test_submit_form(self):
# Django's test client enforces CSRF by default
response = self.client.post('/submit/', \{'data': 'value'\})
self.assertEqual(response.status_code, 200)
# If you need to test CSRF failures:
def test_csrf_failure(self):
from django.test import Client as BaseClient
client = BaseClient(enforce_csrf_checks=True)
response = clientpost \ \
Forms in iframes from different domains can have CSRF issues.
<!-- Parent site: example.com -->
<iframe src="https://other-domain.com/form/"></iframe>
Fix: Configure CSRF for cross-origin
# settings.py for other-domain.com
# Allow embedding from specific domains
X_FRAME_OPTIONS = 'ALLOW-FROM https://example.com'
# Or use Content Security Policy
CSP_FRAME_ANCESTORS = ['https://example.com']
# Adjust CSRF cookie settings
CSRF_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SECURE = True
CSRF_TRUSTED_ORIGINS = ['https://example.com']
<!-- Every POST form should have this -->
<form method="post">
{% csrf_token %}
<!-- form fields -->
</form>
# views.py
from django.shortcuts import render
from .forms import MyForm
def my_view(request):
form = MyForm(request.POST or None)
if request.method == 'POST' and form.is_valid():
form.save()
return render(request, 'form.html', \{'form': form\})
<!-- template.html -->
<form method="post">
\{% csrf_token %\}
\{\{ form.as_p \}\}
<button type="submit">Submit</button>
</form>
// Put this in your main JS file
document.addEventListener('DOMContentLoaded', function() {
const csrftoken = getCookie('csrftoken');
// Set default for all fetch requests
const originalFetch = window.fetch;
window.fetch = function(url, options = \{\}) \{
if (options.method && options.method.toUpperCase() !== 'GET') \{
options. options \\
# AVOID unless absolutely necessary
@csrf_exempt
def my_view(request):
pass
# INSTEAD, fix the CSRF configuration
When you encounter CSRF errors, check:
{% csrf_token %} in your form template?CsrfViewMiddleware in MIDDLEWARE settings?X-CSRFToken header included?CSRF protection is Django's defense against cross-site request forgery attacks. The error means Django didn't receive or couldn't validate the CSRF token.
Most common causes:
{% csrf_token %} in formsQuick fixes:
{% csrf_token %} to all POST formsCSRF_TRUSTED_ORIGINS for your domainsCsrfViewMiddleware is enabled@csrf_exempt unless absolutely necessaryRemember: CSRF protection is a critical security feature. Don't disable it just to make errors go away - fix the underlying configuration instead!