DEV Community

Md Sujauddin Sekh
Md Sujauddin Sekh

Posted on

Using CSRF Protection with Django and AJAX Requests

Using CSRF Protection with Django and AJAX Requests

Django has built-in support for protection against CSRF by using a CSRF token.
It's enabled by default when you scaffold your project using the django-admin
startproject <project>
command, which adds a middleware in settings.py.

Every POST request to your Django app must contain a CSRF token. In a Django
template, you do this by adding {% csrf_token %} to any form that uses the
POST method.

Let's see how that can be done with AJAX from a frontend that is separate from
Django.

Setup

To show how it's done, we will build a simple app. In the backend, there is a
URL of a picture; a GET request will get the picture and a POST request will set
a new URL. The app has no error handling and such things in the frontend or
backend for simplicity.

Right now it has two endpoints:

  • GET /get-picture - gets the URL of an image stored in the server
  • POST /set-picture - sets the URL of an image stored in the server

And initially the backend code looks like this. I have written all the code in
urls.py just to make everything simple.

from django.urls import path, include
from django.http import JsonResponse
import json

picture_url = "https://picsum.photos/id/247/720/405"


def get_picture(request):
    print(picture_url)
    return JsonResponse({"picture_url": picture_url})


def set_picture(request):
    if request.method == "POST":
        global picture_url
        picture_url = json.loads(request.body)["picture_url"]
        picture_url = picture_url
        return JsonResponse({"picture_url": picture_url})


urlpatterns = [
    path("get-picture", get_picture),
    path("set-picture", set_picture)
]
Enter fullscreen mode Exit fullscreen mode

Now, for the frontend I'm showing you only the main functions.


// Get the picture my making a GET request
async function get_picture() {
    const res = await fetch("http://localhost:8000/get-picture");
    const data = await res.json();
    const picture_url = data.picture_url;
    return picture_url;
}

// Tries to set the picture_url with a POST request
async function set_picture(picture_url) {
    const res = await fetch("http://localhost:8000/set-picture", {
        method: "POST",
        body: JSON.stringify({ "picture_url": picture_url })
    })
}

Enter fullscreen mode Exit fullscreen mode

And for CORS reasons, you need to set up CORS in the backend, or else you can't
make a request. Without CORS configuration in the backend, you will get an error
like this.

Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://localhost:8000/get-picture. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing). Status code: 200.
Enter fullscreen mode Exit fullscreen mode

To fix this, we have to add some headers to the responses. To do this, we will
use the django-cors-headers package.

Install and configure django-cors-headers package.

pip install django-cors-headers
Enter fullscreen mode Exit fullscreen mode

Configure in settings.py

INSTALLED_APPS = [
    "corsheaders",
    # and more ...
]

MIDDLEWARE = [
    "corsheaders.middleware.CorsMiddleware",
    # and more...
]

CORS_ALLOWED_ORIGINS = ["http://localhost:4040"] # change the port if you use port other than 4040
Enter fullscreen mode Exit fullscreen mode

Now everything for the GET request will work fine. But if you try to set the
picture URL, in the backend you will see this error.

Forbidden (Origin checking failed - http://localhost:4040 does not match any trusted origins.): /set-picture
[06/Jan/2025 13:10:47] "POST /set-picture HTTP/1.1" 403 2554
Enter fullscreen mode Exit fullscreen mode

And this is what we will fix.

Configure CSRF Protection

As your frontend is separate from Django, you need to manually ask for a CSRF
token. Django will send the token as a cookie by attaching it in the
Set-Cookie headers in the response. The token will be saved in your browser
cookie named csrftoken.

First add you frontend URL to CSRF_TRUSTED_ORIGINS in settings.py

CSRF_TRUSTED_ORIGINS = ["http://localhost:4040"]
Enter fullscreen mode Exit fullscreen mode

Create a new view to return the CSRF token as a cookie.

from django.views.decorators.csrf import ensure_csrf_cookie

@ensure_csrf_cookie
def get_csrf_token(request):
    return JsonResponse({"success": True})

urlpatterns = [
    # more...,
    path("get-csrf-token", get_csrf_token),
]
Enter fullscreen mode Exit fullscreen mode

Now, we have a way to get the CSRF token from the backend. To, get the token in
the frontend add the following code in your backend.

fetch("http://localhost:8000/get-csrf-token", {
    credentials: "include"
});
Enter fullscreen mode Exit fullscreen mode

It makes a request to get a token. With credentials: "include" it tells the
browser that if the response header contains any Set-Cookie header, it should
obey that instruction. If you remove the credentails property, the cookie
won't be set.

If you are curious, look at the network in browser console and check the
headers. You will see a header similar to this.

Set-Cookie: csrftoken=cyjpe3i9Nrq4yTFfnHjY3n5ekfo7blcu; expires=Mon, 05 Jan 2026 13:22:51 GMT; Max-Age=31449600; Path=/; SameSite=Lax
Enter fullscreen mode Exit fullscreen mode

Modify the set-picture function

Now the only thing we have to do is to send the CSRF token with the set-picture
POST call. Modify the set_picture function.

async function set_picture(picture_url) {
    const res = await fetch("http://localhost:8000/set-picture", {
        method: "post",
        credentials: "include",
        headers: {
            'X-CSRFToken': Cookies.get("csrftoken")
        },
        body: JSON.stringify({ "picture_url": picture_url })
    })
}
Enter fullscreen mode Exit fullscreen mode

We need to add an X-CSRFToken header, and its value will be the value of the
csrftoken cookie. To get the cookie, the js-cookie library has been used.

Now the POST request should work fine.

There may be other ways to do this. With this method, there are some cons you
need to be aware of. If you are deploying your frontend and backend on different
domains, there might be a problem with cookies due to browser security and
cookie policy. The browser might not set the CSRF cookie, because it will reject
any third-party cookie. And even if the security is low, you probably won't be
able to read the cookie value with Cookies.get("csrftoken") due to policies
related to cookies.

Source Code: https://github.com/sujaudd1n/tutorials/tree/main/django-ajax-csrf

Learn More

Top comments (0)