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 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's render engine replaces the {% csrf_token %} tag with a hidden input like:

html

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

When the form is submitted:

  • Token is sent 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>

This requests simply fetches all data from the 'products' endpoint. Nothing state-changing.

Example Unsafe POST:

html

1
<form method="post">

POST requests are meant to be state-changing an therefore need a token or the request gets blocked.

Basic Django Form Example:

html

1
2
3
4
5
6
<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

In its 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 → illegal request → request blocked.

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: The token required to make a state-changing request on your behalf is only accessible by you on the valid site and not the attacker on a malicious site.

Using CSRF with JavaScript / AJAX

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

For JavaScript requests, the same principle applies: The CSRF token has to be sent to validate the request.

In .js scripts the token is sent as a header parameter X-CSRFToken

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
<script>
    const csrfToken = "{{ csrf_token }}";
</script>

Or by reading the cookies stored in the browser and retrieving the cookie from there.

A simple .js script to retrieve the crsf token from cookies can be found here: Get csrf-token from browser cookies

Common Mistakes

1. Forgetting Token in POST Form

html

1
<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.

Example:

python

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

@csrf_exempt
def faulty_view():
    ...

Sometimes legitimate for:

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

Only use this decorator when truly justified and not just as a work-around.

4. Forgetting AJAX Headers

POST via JavaScript still needs token.

Include

javascript

1
2
3
4
headers: {
    "X-CSRFToken": csrfToken,
    "Content-Type": "application/json"
}

in your request

5. Removing Middleware

If CSRF middleware is disabled, protection disappears.

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.

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.