In a Django template, what does {% csrf_token %} add to a POST form, and why does the submit fail with 403 without it?
answer
- a hidden field, not a header
- named csrfmiddlewaretoken
- cookie plus a second copy
- safe methods are skipped
- render() passes the request
basics
~20 s{% csrf_token %} renders a hidden csrfmiddlewaretoken input carrying a masked copy of the visitor's CSRF secret. CsrfViewMiddleware answers 403 Forbidden to a POST whose token is missing or does not match the csrftoken cookie.
solid answer
~40 s`CsrfViewMiddleware` is in the `MIDDLEWARE` list that `startproject` generates, and it checks every request whose method is not GET, HEAD, OPTIONS or TRACE. It needs two things: the `csrftoken` cookie holding a random secret, and a second copy of that secret sent with the request. `{% csrf_token %}` supplies the second copy as `<input type="hidden" name="csrfmiddlewaretoken" value="…">`, and rendering it also makes the middleware set the cookie on that response. Without the tag the POST arrives with no token, so the middleware hands the request to `CSRF_FAILURE_VIEW`, which returns 403 "CSRF verification failed. Request aborted." The tag only works when the template is rendered with the request, which `render()`, generic views and the contrib apps do. GET forms need no token, and a form that posts to another site must not carry one.
code
python · 15 linesfrom django.shortcuts import redirect, render
from .forms import FeedbackForm # a ModelForm
def feedback(request):
if request.method == "POST":
form = FeedbackForm(request.POST)
if form.is_valid():
form.save()
return redirect("feedback-thanks")
else:
form = FeedbackForm()
# render() passes the request, so {% csrf_token %} can reach the token
return render(request, "feedback.html", {"form": form})go deeper
Know that every POST form in a Django template needs {% csrf_token %} inside the form tag, and that a missing tag produces the 403 CSRF verification failed page.
Explain the two halves: the csrftoken cookie and the csrfmiddlewaretoken field, which methods are checked, and why render() is needed for the tag to produce anything.
Read the 403 reason string to diagnose missing cookies, stale tokens after login rotation, and templates rendered without a request, instead of reaching for csrf_exempt.
Set the team rule that state-changing views accept only unsafe methods and that CSRF exemptions need review, since the token is only as good as the GET-is-safe discipline behind it.
## What the tag renders Django's **CSRF protection** has two halves that must agree on every state-changing request: - a **CSRF cookie**, named `csrftoken` by default (`CSRF_COOKIE_NAME`), holding a random 32-character secret; - a **second copy of that secret** sent in the request itself. The `{% csrf_token %}` template tag produces the second copy for ordinary HTML forms. Inside a `<form method="post">` it renders exactly one element: ```html <input type="hidden" name="csrfmiddlewaretoken" value="…64 characters…"> ``` The value is the secret **masked** with a fresh random mask each time the page is rendered, so it looks different on every load while still carrying the same secret. Rendering the tag calls `django.middleware.csrf.get_token()`, and that call is what tells the middleware to send the `csrftoken` cookie on the response. A page that never renders the tag, and never calls `get_token()` any other way, may never receive the cookie at all. ## What CsrfViewMiddleware checks `django.middleware.csrf.CsrfViewMiddleware` is in the default `MIDDLEWARE` of a new project. It does its checking in `process_view()`, after URL resolution and before your view runs, in this order: 1. If the view is marked with `csrf_exempt`, stop and let it through. 2. If the method is GET, HEAD, OPTIONS or TRACE, let it through. Every other method, including POST, PUT, PATCH and DELETE, is treated as unsafe. 3. If the request carries an `Origin` header, it must match the site's own origin or `CSRF_TRUSTED_ORIGINS`; over HTTPS with no `Origin`, the `Referer` is checked strictly instead. 4. The CSRF cookie must be present, or the request is rejected with "CSRF cookie not set." 5. The token must be present, read from the POST field `csrfmiddlewaretoken` or, failing that, from the `X-CSRFToken` header. 6. The token is unmasked and its secret compared with the cookie's secret. Any failure hands the request to the view named by `CSRF_FAILURE_VIEW` (default `django.views.csrf.csrf_failure`), which returns **403 Forbidden** and logs a warning to the `django.security.csrf` logger. ## Reading the 403 With `DEBUG = True`, the failure page shows a *reason* string. The common ones for a template form: | Reason on the 403 page | Usual cause | |---|---| | `CSRF token missing.` | the form has no `{% csrf_token %}` | | `CSRF cookie not set.` | the page that rendered the form never set the cookie, or the browser refused it | | `CSRF token from POST incorrect.` | the page is stale, for example opened before the user logged in | The last one surprises people: `django.contrib.auth.login()` calls `rotate_token()`, so a form rendered before login carries a token for the old secret. ## Making the tag work The tag reads `csrf_token` from the template context, which the built-in `csrf` context processor fills from the request. That only happens when the template is rendered with the request: - `render(request, template, context)` and every generic view do this for you; - a bare `Template(...).render(Context(...))` does not, and the tag renders nothing; - with the Jinja2 backend, use `{{ csrf_input }}` instead; it renders the same hidden input. ## Where not to use it - **GET forms** (search boxes, filters) need no token, because the middleware never checks safe methods. Adding one only copies the token into URLs, browser history and logs. - **Forms posting to another site** must not carry it: the token would be handed to that third party. Django's how-to guide warns against exactly this. ## Common mistakes - Believing CSRF only matters for logged-in users. The check applies to anonymous POSTs too, which is how Django also blocks **login CSRF**. - Fixing the 403 by removing the middleware or adding `csrf_exempt` rather than adding the tag. - Expecting the token to be tied to the session. By default it lives in its own long-lived cookie (`CSRF_COOKIE_AGE`, about a year), independent of the session. - Caching a form page with a per-view decorator such as `cache_page`. The cached HTML can be stored before the middleware has set the cookie and the `Vary: Cookie` header, so later visitors get a token with no matching cookie. Django's how-to says to put `csrf_protect` beneath `cache_page` on such views. - Shipping the default failure page to users. It is plain and developer-oriented; point `CSRF_FAILURE_VIEW` at your own view, or add a `403_csrf.html` template, which the default view renders when it exists.
- A user logs in on one tab, then submits a Django form left open on another tab and gets a 403. Why?`django.contrib.auth.login()` calls `rotate_token()`, which generates a new CSRF secret and sends a new `csrftoken` cookie. The form on the other tab still carries a token masked from the old secret, so the comparison fails with "CSRF token from POST incorrect." Reloading the page renders a fresh token. The rotation is deliberate: it stops a token planted before login from being usable afterwards.
- Does a Django form with method="get" need {% csrf_token %}?No. `CsrfViewMiddleware` lets GET, HEAD, OPTIONS and TRACE through without any token check, on the assumption that those requests have no side effects. Putting the tag in a GET form only copies the token into the query string, where it lands in history, logs and `Referer` headers. The real rule is the reverse: a view that changes state must not accept GET.
- How do you add the CSRF field in a template rendered by Django's Jinja2 backend?Use `{{ csrf_input }}`, which the Jinja2 backend puts in every template context when a request is available; it renders the same hidden `csrfmiddlewaretoken` input as `{% csrf_token %}`. The raw value is also available as `{{ csrf_token }}` if you need it in a data attribute.
saying these in an interview costs you the question
- CSRF tokens are only needed once a user is logged in
- A normal HTML form sends the token in an X-CSRFToken header
- Adding {% csrf_token %} to GET search forms makes them safer
- Django only checks POST, so PUT and DELETE need no token
- A CSRF 403 always means the user's session has expired
- Remove CsrfViewMiddleware when forms keep failing with 403