'Django (DRF) & React - Forbidden (CSRF cookie not set)

There are tens of questions that are essentially identical to the one I'm asking. However, none of their answers seem to be working for me.

I have a React front-end where I am using axios to send requests to the back-end. Example
const request = await axios.post('${BASE_URL}/logout/')

Most of the Django Rest Framework endpoints are made with ViewSets. However, I have a few that are custom and mostly made for authentication.

path('createaccount/', views.create_account),
path('me/', views.current_user),
path('logout/', views.logout),
path('login/', views.login),
path('resetpassword', views.reset_password),

For the development of this project I've included @csrf_exempt above these views because I didn't want to deal with it at the time. Now I'm nearing deployment and it's time to figure it out.

Some answers say I need to get a CSRF Token from Django which is stored in cookies and I need to pass that in the header of each request. Some answers say all I need to do is configure axios like

axios.defaults.xsrfHeaderName = "X-CSRFTOKEN";
axios.defaults.xsrfCookieName = "XCSRF-TOKEN";

And it will "just work". I've tried adjusting my CSRF_COOKIE_NAME to various values to get this to work too.

Some answers even say to keep @csrf_exempt but that sounds like a very, very bad idea.

Do I actually need to generate/get a CSRF cookie? Do I include it with every request? Or is it just a configuration of axios?



Solution 1:[1]

To make CSRF protection work you will need CSRF cookie sent from Django to React as a response to some request (like login or sth else). It will set cookie using Set-Cookie on frontend side. So make sure that you have a view that does that on Django side. If not, create a view that as response generates that token.

How Django (4.04) CSRF validation work (simplified, based on middleware/csrf.py):

  1. gets CSRF token from cookie (so frontend needs to resend it back on another request) - it might also get it from session but in case of React I would not use it

    def _get_token(self, request):
        ....
        try:
            cookie_token = request.COOKIES[settings.CSRF_COOKIE_NAME]
        except KeyError:
            return None
    
  2. Compares that cookie CSRF token with non-cookie token from request:

    def _check_token(self, request):
        # Access csrf_token via self._get_token() as rotate_token() may have
        # been called by an authentication middleware during the
        # process_request() phase.
        try:
            csrf_token = self._get_token(request)
        except InvalidTokenFormat as exc:
            raise RejectRequest(f"CSRF cookie {exc.reason}.")
    
        if csrf_token is None:
            # No CSRF cookie. For POST requests, we insist on a CSRF cookie,
            # and in this way we can avoid all CSRF attacks, including login
            # CSRF.
            raise RejectRequest(REASON_NO_CSRF_COOKIE)
    
        # Check non-cookie token for match.
        request_csrf_token = ""
        if request.method == "POST":
            try:
                request_csrf_token = request.POST.get("csrfmiddlewaretoken", "")
            except UnreadablePostError:
                # Handle a broken connection before we've completed reading the
                # POST data. process_view shouldn't raise any exceptions, so
                # we'll ignore and serve the user a 403 (assuming they're still
                # listening, which they probably aren't because of the error).
                pass
    
        if request_csrf_token == "":
            # Fall back to X-CSRFToken, to make things easier for AJAX, and
            # possible for PUT/DELETE.
            try:
                request_csrf_token = request.META[settings.CSRF_HEADER_NAME]
            except KeyError:
                raise RejectRequest(REASON_CSRF_TOKEN_MISSING)
            token_source = settings.CSRF_HEADER_NAME
        else:
            token_source = "POST"
    
        try:
            request_csrf_token = _sanitize_token(request_csrf_token)
        except InvalidTokenFormat as exc:
            reason = self._bad_token_message(exc.reason, token_source)
            raise RejectRequest(reason)
    
        if not _does_token_match(request_csrf_token, csrf_token):
            reason = self._bad_token_message("incorrect", token_source)
            raise RejectRequest(reason)
    

As you can see you either need to include csrfmiddlewaretoken in POST request or include it in header with key: settings.CSRF_HEADER_NAME and value read from cookies on front-end side.

So for example you set withCredentials: true (to include initial cookie), read that initial CSRF cookie in React and add to header in axios request at specific key.

When in question, I would just debug request setting up breakpoints in this code of Django in middleware/csrf.py and you can trace what is missing and why CSRF validation fails.

Solution 2:[2]

I've got this problem once, I was using token authentication. That's how I solved it. But not sure If it is the best idea. I only used csrf_exempt for this view and all others views are viewsets.

@csrf_exempt
def get_current_user(request, *args, **kwargs):
    if request.method == 'GET':
        user = request.user
        serializer = UserDataSerializer(user)
        return JsonResponse(serializer.data, safe=False)

My middleware in settings.py

MIDDLEWARE = [
   'django.middleware.security.SecurityMiddleware',
   'django.contrib.sessions.middleware.SessionMiddleware',
   'corsheaders.middleware.CorsMiddleware',
   # 'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
   'django.middleware.common.CommonMiddleware',
   'django.middleware.csrf.CsrfViewMiddleware',
   'django.contrib.auth.middleware.AuthenticationMiddleware',
   'django.middleware.locale.LocaleMiddleware',
   'oauth2_provider.middleware.OAuth2TokenMiddleware',
   'django.contrib.messages.middleware.MessageMiddleware',
   'django.middleware.clickjacking.XFrameOptionsMiddleware',
   'auditlog.middleware.AuditlogMiddleware',
]

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Krzysiek Kucharski
Solution 2 RonanFelipe