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

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.