Fix: Django Forbidden (403) CSRF verification failed
Quick Answer
How to fix Django 403 CSRF verification failed error caused by missing CSRF tokens, AJAX requests, cross-origin issues, HTTPS misconfig, and session problems.
The Error
Your Django form submission or POST request fails with:
Forbidden (403)
CSRF verification failed. Request aborted.
Reason given for failure:
CSRF token missing or incorrect.Or variations:
Forbidden (403)
CSRF verification failed. Request aborted.
Reason given for failure:
CSRF cookie not set.Forbidden (403)
CSRF verification failed. Request aborted.
Reason given for failure:
Referer checking failed - https://example.com does not match any trusted origins.Django’s Cross-Site Request Forgery protection rejected the request because it could not verify that the request is legitimate and not a forged attack.
Why This Happens
Django requires a CSRF token on every POST, PUT, PATCH, and DELETE request (any state-changing request). This token proves the request came from your own site, not from a malicious third-party page.
The CSRF check verifies:
- A CSRF cookie is set in the browser.
- A matching CSRF token is sent in the form data or header.
- The
Refererheader matches a trusted origin (for HTTPS requests).
Common causes:
- Missing
{% csrf_token %}in the form template. - AJAX request without the CSRF header.
CSRF_TRUSTED_ORIGINSnot configured for your domain.- Cookie not set. The
csrftokencookie was blocked or expired. - HTTPS/HTTP mismatch. The request origin does not match the trusted origins.
- Caching issue. A cached page serves a stale CSRF token.
- Cross-origin request. The frontend and backend are on different domains.
Fix 1: Add CSRF Token to Forms
The most common fix. Every HTML form that uses POST needs the token:
Broken:
<form method="post" action="/submit/">
<input type="text" name="message">
<button type="submit">Send</button>
</form>Fixed:
<form method="post" action="/submit/">
{% csrf_token %}
<input type="text" name="message">
<button type="submit">Send</button>
</form>{% csrf_token %} renders a hidden input field:
<input type="hidden" name="csrfmiddlewaretoken" value="abc123...">Pro Tip: Always include
{% csrf_token %}in every<form method="post">in your Django templates. GET forms do not need it because GET requests should not change server state.
Fix 2: Fix AJAX/Fetch Requests
JavaScript requests need to send the CSRF token in a header:
Using fetch:
// 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;
}
fetch('/api/data/', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRFToken': getCookie('csrftoken'),
},
body: JSON.stringify({ message: 'hello' }),
});Using Axios (global setup):
import axios from 'axios';
axios.defaults.xsrfCookieName = 'csrftoken';
axios.defaults.xsrfHeaderName = 'X-CSRFToken';
// Now all requests automatically include the CSRF token
axios.post('/api/data/', { message: 'hello' });Using jQuery:
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!/^(GET|HEAD|OPTIONS|TRACE)$/i.test(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", getCookie('csrftoken'));
}
}
});Common Mistake: Forgetting that the CSRF cookie must exist before you can read it. If the user has not loaded any Django page yet, the cookie may not be set. Use the
ensure_csrf_cookiedecorator on a view to force-set the cookie.
Fix 3: Configure CSRF_TRUSTED_ORIGINS
For Django 4.0+, you must list trusted origins explicitly:
# settings.py
CSRF_TRUSTED_ORIGINS = [
'https://example.com',
'https://www.example.com',
'https://app.example.com',
]
# For development
CSRF_TRUSTED_ORIGINS = [
'http://localhost:3000',
'http://localhost:8000',
'http://127.0.0.1:8000',
]The “Referer checking failed” error specifically means CSRF_TRUSTED_ORIGINS is missing or does not include the requesting origin.
Include the scheme (http/https):
# Wrong — missing scheme
CSRF_TRUSTED_ORIGINS = ['example.com']
# Correct
CSRF_TRUSTED_ORIGINS = ['https://example.com']Fix 4: Fix Cross-Origin Frontend/Backend
When your React/Vue/Angular frontend is on a different origin than Django:
# settings.py
# Allow the frontend origin
CORS_ALLOWED_ORIGINS = [
'http://localhost:3000', # React dev server
]
CSRF_TRUSTED_ORIGINS = [
'http://localhost:3000',
]
# Allow credentials (cookies) in cross-origin requests
CORS_ALLOW_CREDENTIALS = True
# Ensure CSRF cookie is accessible cross-origin
CSRF_COOKIE_SAMESITE = 'Lax' # or 'None' for cross-site (requires Secure)
CSRF_COOKIE_HTTPONLY = False # JavaScript needs to read it
SESSION_COOKIE_SAMESITE = 'Lax'Install django-cors-headers:
pip install django-cors-headersINSTALLED_APPS = [
'corsheaders',
...
]
MIDDLEWARE = [
'corsheaders.middleware.CorsMiddleware', # Must be before CommonMiddleware
'django.middleware.common.CommonMiddleware',
...
]Fix 5: Fix for API Views (DRF)
Django REST Framework can use different authentication that does not need CSRF:
For token-based authentication:
# settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': [
'rest_framework.authentication.TokenAuthentication',
# SessionAuthentication requires CSRF — remove it if using tokens only
],
}Exempt specific views from CSRF:
from django.views.decorators.csrf import csrf_exempt
@csrf_exempt
def webhook_view(request):
"""External webhook — no CSRF token available."""
# Verify the request authenticity another way (signature, API key)
passFor class-based views:
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_exempt
@method_decorator(csrf_exempt, name='dispatch')
class WebhookView(View):
def post(self, request):
passWarning: Only use @csrf_exempt for views that have alternative authentication (API keys, webhook signatures, token auth). Never exempt regular form views.
Fix 6: Fix Cookie Issues
If the error says “CSRF cookie not set”:
Force the cookie to be set:
from django.views.decorators.csrf import ensure_csrf_cookie
@ensure_csrf_cookie
def get_csrf_token(request):
"""Set the CSRF cookie for JavaScript clients."""
return JsonResponse({'status': 'ok'})Check cookie settings:
# settings.py
# Cookie name (default: 'csrftoken')
CSRF_COOKIE_NAME = 'csrftoken'
# For HTTPS only
CSRF_COOKIE_SECURE = True # Only sent over HTTPS
# Allow JavaScript to read the cookie
CSRF_COOKIE_HTTPONLY = False # Must be False for AJAX
# Cookie domain
CSRF_COOKIE_DOMAIN = '.example.com' # Share across subdomains
# SameSite attribute
CSRF_COOKIE_SAMESITE = 'Lax' # Default, good for most casesCommon issue — CSRF_COOKIE_SECURE = True on HTTP:
# This blocks the cookie on HTTP (development)
CSRF_COOKIE_SECURE = True
# Fix: Only enable in production
CSRF_COOKIE_SECURE = not DEBUGFix 7: Fix Caching Issues
Cached pages can serve stale CSRF tokens:
# Never cache pages with forms
from django.views.decorators.cache import never_cache
@never_cache
def my_form_view(request):
return render(request, 'form.html')With template fragment caching:
{# Don't cache the form part #}
<form method="post">
{% csrf_token %}
{# The rest of the form #}
</form>
{# Cache other parts #}
{% cache 600 sidebar %}
{# Sidebar content #}
{% endcache %}Fix 8: Debug CSRF Failures
Enable detailed CSRF failure logging:
# settings.py
LOGGING = {
'version': 1,
'handlers': {
'console': {'class': 'logging.StreamHandler'},
},
'loggers': {
'django.security.csrf': {
'handlers': ['console'],
'level': 'DEBUG',
},
},
}Custom CSRF failure view for debugging:
# settings.py
CSRF_FAILURE_VIEW = 'myapp.views.csrf_failure'
# myapp/views.py
def csrf_failure(request, reason=""):
from django.http import JsonResponse
return JsonResponse({
'error': 'CSRF verification failed',
'reason': reason,
'cookie_present': bool(request.COOKIES.get('csrftoken')),
'origin': request.META.get('HTTP_ORIGIN', 'none'),
'referer': request.META.get('HTTP_REFERER', 'none'),
}, status=403)Still Not Working?
Check for reverse proxy headers. If behind Nginx or a load balancer:
# settings.py
SECURE_PROXY_SSL_HEADER = ('HTTP_X_FORWARDED_PROTO', 'https')
USE_X_FORWARDED_HOST = TrueCheck for browser privacy settings. Some browsers block third-party cookies, which can affect CSRF cookies in cross-origin setups.
Check for middleware order. CsrfViewMiddleware must be in the correct position:
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware', # Before CommonMiddleware
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware', # After SessionMiddleware
'django.contrib.auth.middleware.AuthenticationMiddleware',
...
]For Django database errors, see Fix: Django OperationalError: no such table. For Python import issues, see Fix: Python ModuleNotFoundError: No module named.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: AWS Lambda Unable to import module / Runtime.ImportModuleError
How to fix the AWS Lambda Runtime.ImportModuleError and Unable to import module error caused by wrong handler paths, missing dependencies, layer issues, and packaging problems.
Fix: Python TypeError: unhashable type: 'list'
Learn why Python raises TypeError unhashable type list, dict, or set and how to fix it when using dictionary keys, sets, groupby, dataclasses, and custom classes.
Fix: FastAPI 422 Unprocessable Entity (validation error)
How to fix FastAPI 422 Unprocessable Entity error caused by wrong request body format, missing fields, type mismatches, query parameter errors, and Pydantic validation.
Fix: Python RuntimeError: no running event loop / This event loop is already running
How to fix Python asyncio RuntimeError no running event loop and event loop already running caused by mixing sync and async code, Jupyter, and wrong loop management.