Understanding csrf_token in Django: What It Is, Why It Exists, and How to Use It

Many developers first see csrf_token as a mysterious line copied from tutorials. In reality, it is a critical security feature protecting authenticated users from forged actions.

When working with forms in Django, one of the first template tags developers encounter is:

{% csrf_token %}

Many beginners add it because tutorials say they must, often without understanding what it does or why it matters. That token is not cosmetic. It is part of a critical web security mechanism designed to protect users and applications from Cross-Site Request Forgery (CSRF) attacks.

A full article on Django's templatetags can be found here: Django's Basic Templatetags

This article explains:

  • What CSRF is
  • Why csrf_token exists
  • How Django uses it
  • How to use it in forms and JavaScript
  • Common mistakes
  • Best practices

What Is CSRF?

CSRF stands for: Cross-Site Request Forgery

It is an attack where a malicious website tricks a logged-in user’s browser into sending unwanted requests to another trusted site.

Example Scenario

Imagine a user is logged into:

bank.com

Their browser stores authentication cookies.

Now the user visits a malicious site containing:

html

1
2
3
4
5
6
7
8
<form action="https://bank.com/transfer/" method="POST">
    <input type="hidden" name="amount" value="5000">
    <input type="hidden" name="to" value="attacker">
</form>

<script>
document.forms[0].submit()
</script>

The browser may automatically send:

  • Session cookies
  • Authentication cookies

So the trusted site thinks the request came from the real user. That is CSRF.

A full article covering CSRF attacks in detail -> CSRF attacks

Why Cookies Cause This

Browsers automatically attach cookies to requests for matching domains. That is convenient for authentication—but dangerous if requests can be forged.

Why csrf_token Exists

Django prevents forged requests by requiring a secret token that:

  • The attacker cannot guess
  • Must be submitted with unsafe requests
  • Must match the user's session/browser state

If the token is missing or invalid, Django blocks the request.

The Django Solution

In templates:

html

1
2
3
4
<form method="post">
    {% csrf_token %}
    ...
</form>

Django inserts a hidden input like:

html

1
<input type="hidden" name="csrfmiddlewaretoken" value="abc123xyz...">

When the form is submitted:

  • Token goes with request body
  • Django verifies it
  • If valid → request allowed
  • If invalid → request rejected

Why GET Usually Does Not Need It

CSRF protection typically applies to state-changing requests:

  • POST
  • PUT
  • PATCH
  • DELETE

Not usually: - GET

Because GET should only retrieve data, never modify it.

Example Safe GET:

html

1
<a href="/products/">Products</a>

Example Unsafe POST:

html

1
<form method="post">

POST request needs a token or request gets blocked.

Basic Django Form Example:

html

1
2
3
4
5
6
7
8
<form method="post">
    {% csrf_token %}

    <input type="text" name="name">
    <button type="submit">
        Save
    </button>
</form>

Without the token, Django usually returns:

403 Forbidden
CSRF verification failed

Where Django Handles This

Through middleware:

'django.middleware.csrf.CsrfViewMiddleware'

Usually enabled by default in settings.py

How Validation Works (Simplified)

Django checks:

  • Token exists
  • Token matches expected secret
  • Request origin/referrer rules (HTTPS contexts)
  • Request method requires protection

If checks fail → block request.

Why Attackers Cannot Easily Fake It

Attackers can cause requests to be sent, but they usually cannot read your trusted site’s pages due to browser same-origin policy. That means they cannot easily obtain the valid CSRF token. That is the defense model.

Using CSRF with JavaScript / AJAX

Modern apps often submit requests with JavaScript instead of normal forms.

Example with fetch():

javascript

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
fetch("/api/save/", {
    method: "POST",
    headers: {
        "X-CSRFToken": csrfToken,
        "Content-Type": "application/json"
    },
    body: JSON.stringify({
        name: "Alice"
    })
});

Where to Get the Token

Often from cookie or template output.

Example in template:

javascript

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
<script>
    const csrfToken = "{{ csrf_token }}";
</script>

Or by reading Djangos CSRF cookie in JavaScript.


###Common Mistakes
####1. Forgetting Token in POST Form
```html
<form method="post">

No token → 403 error.

2. Using GET for Data Modification

Bad:

html

1
<a href="/delete/5/">Delete</a>

Use POST/DELETE with CSRF protection.

3. Disabling CSRF to "Fix Errors"

Django provides a decorator that can be used to mark certain endpoints as 'csrf exempt', meaning that even for state modifying requests no csrf token is required.

Bad habit is to use:

html

1
2
3
@csrf_exempt
def faulty_view():
    ...

if POST requests cause errors.

Only use this decorator when truly justified.

4. Forgetting AJAX Headers

POST via JavaScript still needs token.

5. Removing Middleware

If CSRF middleware is disabled, protection disappears.

What @csrf_exempt Does

Django allows:

python

1
2
3
4
5
from django.views.decorators.csrf import csrf_exempt

@csrf_exempt
def webhook(request):
    ...

Sometimes legitimate for:

  • Third-party webhooks
  • External machine-to-machine endpoints
  • APIs using token auth without cookies

Use carefully, and only when truly justified.

Django Is Strong in terms of Security

Django has long prioritized secure defaults.

By default it gives:

  • CSRF middleware
  • Template tag support
  • Secure cookie integrations
  • Clear failure behavior

This is one reason Django is respected for security.

Without CSRF protection:

  • Account changes can be forged
  • Purchases can be triggered
  • Messages can be posted
  • Sensitive actions can be abused

With protection:

  • Requests need trusted token context

csrf_token function as proof that the form/request was generated by your real Django site, not a malicious third-party page.

Best Practices

Always Include in POST Forms

{% csrf_token %}

Keep Middleware Enabled

Default Django config is usually correct.

Use Proper HTTP Methods

  • GET = read
  • POST = modify

Handle AJAX Correctly

Send X-CSRFToken.

Avoid csrf_exempt Unless Necessary

Security exceptions should be deliberate.

Join the Newsletter

Practical insights on Django, backend systems, deployment, architecture, and real-world development — delivered without noise.

Get updates when new guides, learning paths, cheat sheets, and field notes are published.

No spam. Unsubscribe anytime.



There is no third-party involved so don't worry - we won't share your details with anyone.