Django CSRF verification failed (tested)
Django's 403 CSRF verification failed page has one of a few reasons: CSRF cookie not set, CSRF token missing, token incorrect, or over HTTPS Origin checking failed. Here is each one reproduced, with its fix.
Setup
A single-file Django app with CsrfViewMiddleware, a /form/ view that issues the csrftoken cookie, and a /submit/ view that accepts POSTs. The test client normally skips CSRF checks, so enforce_csrf_checks=True turns them on. The log: lines are Django's own django.security.csrf warnings, which carry the exact reason shown on the 403 page.
import logging, sys
import django
from django.conf import settings
settings.configure(ROOT_URLCONF=__name__, SECRET_KEY='dev', ALLOWED_HOSTS=['*'],
MIDDLEWARE=['django.middleware.csrf.CsrfViewMiddleware'])
django.setup()
logging.basicConfig(stream=sys.stdout, format='log: %(levelname)s %(message)s')
from django.urls import path
from django.http import HttpResponse
from django.middleware.csrf import get_token
def form(request): get_token(request); return HttpResponse('form') # sets csrftoken cookie
def submit(request): return HttpResponse('saved')
urlpatterns = [path('form/', form), path('submit/', submit)]
from django.test import Client
1. The token reasons: cookie not set, token missing, incorrect
from common import *
c = Client(enforce_csrf_checks=True)
print(1, c.post('/submit/', {'x': 1}).status_code) # no cookie at all
c.get('/form/') # now has csrftoken cookie
token = c.cookies['csrftoken'].value
print(2, c.post('/submit/', {'x': 1}).status_code) # cookie, no token
print(3, c.post('/submit/', {'x': 1, 'csrfmiddlewaretoken': 'x' * 32}).status_code)
print(4, c.post('/submit/', {'x': 1, 'csrfmiddlewaretoken': token}).status_code)
print(5, c.post('/submit/', {'x': 1}, headers={'X-CSRFToken': token}).status_code) # AJAX header
Output:
log: WARNING Forbidden (CSRF cookie not set.): /submit/
1 403
log: WARNING Forbidden (CSRF token missing.): /submit/
2 403
log: WARNING Forbidden (CSRF token from POST incorrect.): /submit/
3 403
4 200
5 200
- CSRF cookie not set: the browser never got (or did not send) the
csrftokencookie. The page with the form must be rendered by Django with{% csrf_token %}(or callget_token()), and the cookie must survive: checkCSRF_COOKIE_SECUREon plain HTTP, the cookie domain, and that a frontend on another port sends cookies (credentials: 'include'). - CSRF token missing: the cookie is there but the request has no
csrfmiddlewaretokenfield and noX-CSRFTokenheader. Add{% csrf_token %}inside the<form>, or send the header from JavaScript (case 5). - CSRF token from POST incorrect: a token was sent but does not match the cookie, e.g. a cached page with an old token, or a hard-coded value.
2. HTTPS: Origin checking failed - does not match any trusted origins
from common import *
c = Client(enforce_csrf_checks=True)
c.get('/form/', secure=True); token = c.cookies['csrftoken'].value
for origin in ['https://testserver', 'https://app.example.com', 'http://testserver']:
r = c.post('/submit/', {'csrfmiddlewaretoken': token}, secure=True, headers={'Origin': origin})
print(origin, r.status_code)
settings.CSRF_TRUSTED_ORIGINS = ['https://app.example.com'] # scheme required
c = Client(enforce_csrf_checks=True) # new client: middleware caches the list at first use
c.get('/form/', secure=True); token = c.cookies['csrftoken'].value
r = c.post('/submit/', {'csrfmiddlewaretoken': token}, secure=True, headers={'Origin': 'https://app.example.com'})
print('trusted', r.status_code)
Output:
https://testserver 200
log: WARNING Forbidden (Origin checking failed - https://app.example.com does not match any trusted origins.): /submit/
https://app.example.com 403
log: WARNING Forbidden (Origin checking failed - http://testserver does not match any trusted origins.): /submit/
http://testserver 403
trusted 200
Over HTTPS, Django also compares the Origin header with the request's own host. A form posted from another domain, or a proxy that terminates TLS so Django sees a different scheme or host, fails here even with a valid token. Add the full origin, including the scheme, to CSRF_TRUSTED_ORIGINS, e.g. ['https://app.example.com']. Behind a proxy, also make Django see the real scheme (SECURE_PROXY_SSL_HEADER) and host (USE_X_FORWARDED_HOST) only if your proxy sets those headers. Restart the server after changing the setting: as the block shows, a client whose middleware is already loaded kept rejecting the origin until a new one was created.
Don't just add @csrf_exempt
@csrf_exempt removes the protection for that view. It is only appropriate for endpoints that do not use cookie-based sessions (e.g. a webhook verified by a signature or a token-authenticated API).
Related: Django NoReverseMatch, Flask CORS error on localhost.
Frequently asked questions
How do I fix 'CSRF cookie not set' in Django?
Render the form page through Django with {% csrf_token %} so the csrftoken cookie is issued, and make sure the browser keeps and sends it (CSRF_COOKIE_SECURE on HTTP, cross-origin fetch needs credentials).
What does 'Origin checking failed - does not match any trusted origins' mean?
On HTTPS the Origin header differs from the host Django sees. Add the full origin with scheme, like https://app.example.com, to CSRF_TRUSTED_ORIGINS.
Written with AI assistance. Measured on 2026-10-23 with Django 6.1.2 and Python 3.14.7 (python:3-alpine in Docker, Django test client). Each block was run verbatim and the output pasted unedited. PlainHeaders is our extension; Python and Django are free.