theberzi
theberzi

Reputation: 2695

Keep getting 403 "CSRF token missing or incorrect" in Django + Vue setup

I have searched through other similar questions but none of the solutions worked nor gave me any insight into what may be happening.

My setup is a Vue frontend (with its own routing) plus a Django backend and API. Any GET route I try works as expected, but POST ones require CSRF protection. I have a custom render function I call on the route that brings to the index (which is then going to be handled by Vue), where I provide the CSRF token like so:

def custom_render(request):
    # ...

    # from django.middleware.csrf
    get_token(request)

    # from django.shortcuts
    return render(request, template)

This sets a cookie csrftoken with the CSRF token, which seems to work correctly, as I can see it in the devtools and if I delete it, it's there again when I refresh. Here are my relevant Django settings.py:

# This one is True in production, but for now I'm testing locally
CSRF_COOKIE_SECURE = False

CSRF_HEADER_NAME = "X-CSRFToken"

# I tried playing with both these options' values to no avail
CSRF_USE_SESSIONS = False
CSRF_COOKIE_HTTPONLY = False


MIDDLEWARE = [
    "django.middleware.security.SecurityMiddleware",
    "django.contrib.sessions.middleware.SessionMiddleware",
    "django.middleware.common.CommonMiddleware",
    "django.middleware.csrf.CsrfViewMiddleware",
    "django.contrib.auth.middleware.AuthenticationMiddleware",
    "django.contrib.messages.middleware.MessageMiddleware",
    "django.middleware.clickjacking.XFrameOptionsMiddleware",
    "django.middleware.common.BrokenLinkEmailsMiddleware",
]

On the Vue side (Typescript) I'm sending a request using this:

function getCsrfToken() {
  return document.cookie.match("(^|;)\\s*" + "csrftoken" + "\\s*=\\s*([^;]+)")?.pop()
}

fetch(
  "api/my-route/",
  {
    method: "POST",
    headers: {
      "X-CSRFToken": getCsrfToken(),
      "Content-Type": "application/json",
      "Accept": "application/json",
    },
    mode: "same-origin",
    body: JSON.stringify(dataToSend),
  },
)

This also works as intended, as I can see in the devtools that the request does contain the X-CSRFToken header with the same content as the csrftoken cookie. However, the response is nonetheless a 403 that claims the token is missing or incorrect. I'm not sure whether Django thinks it's missing or whether it thinks it's incorrect, so I'm unsure how to proceed. Wiring up a debugger is impractical because I don't know what internal method I should halt execution at, so I'm stuck.

EDIT: Other things I have tried recently:

Unfortunately none of these changed the outcome: when I do have the cookie available, its value doesn't seem to satisfy Django.

Upvotes: 3

Views: 1506

Answers (1)

theberzi
theberzi

Reputation: 2695

I found the issue: the CSRF_HEADER_NAME = "X-CSRFToken" in my settings does not take into account the fact that Django, so much for "explicit is better than implicit", implicitly normalises all header names such that the token in the request will end up looking like HTTP_X_CSRFTOKEN, but doesn't bother doing the same to the custom name, so the two won't match by the time Django compares them.

Upvotes: 1

Related Questions