pythondjangodjango-rest-frameworkdjango-csrfcsrf-token

Forbidden (CSRF cookie not set.) when trying to connect from a desktop app


I'm developing a desktop application where users can log in using credentials from my Django website's database. The goal is for users to be able to create posts on the Django website (forum) through the desktop application.

In the desktop app's code, I have the following code, which successfully retrieves a CSRF token from Django:

def get_csrf_token():
    
    # Make a GET request to your Django server to fetch the CSRF token
    response = requests.get('http://127.0.0.1:8000/accounts/get-csrf-token/')
     
    # Check if the request was successful (status code 200)
    if response.status_code == 200:
        # Extract the CSRF token from the response JSON
        csrf_token = response.json().get('csrf_token')
        print("CSRF TOKEN: ", csrf_token)
        return csrf_token
    else:
        # Handle errors, such as failed requests
        print(f"Failed to fetch CSRF token. Status code: {response.status_code}")
        return None

And the following code which should send the data (username, password, CSRF token) to Django:

def login(self):

        # Retrieve username and password from input fields
        username = self.username_input.text()
        password = self.password_input.text()   
               
        # Send login request to Django server
        url = "http://127.0.0.1:8000/accounts/login/"
        data = {
            "username": username, 
            "password": password,
            }
        
        # Define the headers with the CSRF token
        headers = {
            'X-CSRFToken': get_csrf_token()
        }
        print("x-csrftoken: ", headers['X-CSRFToken'])
        response = requests.post(url, data=data, headers=headers)
        
        if response.status_code == 200:
            # Authentication successful
            self.accept()
        else:
            # Authentication failed
            QMessageBox.warning(self, "Login Failed", "Invalid username or password")

The code above is sending a request to http://127.0.0.1:8000/accounts/login/, which is linked to CustomLoginView in my urls.py:

path('login/', CustomLoginView.as_view(template_name='accounts/login.html'), name='login')

And this the code in my views.py:

class CustomAuthenticationForm(AuthenticationForm):
    def clean_username(self):
        print("Entering clean_username method.......") 
        username = self.cleaned_data.get('username')
        if username:
            # Perform a case-insensitive lookup for the username
            return CustomUser.objects.filter(username__iexact=username).first()
        return None

class CSRFTokenView(APIView):
    authentication_classes = [SessionAuthentication]

    def get(self, request, format=None):
        print("Entering get method......")
        csrf_token = get_token(request)
        print("csrf token inside get method of CSRFTokenView: ", csrf_token)
        return Response({'csrf_token': csrf_token})

    
class CustomLoginView(LoginView):
     
    authentication_form = CustomAuthenticationForm
    
    def get_success_url(self):
        print("Entering get_success_url method......")
        return reverse_lazy('thread_list')

And when I try to login via the desktop app, I get the following message from Django:

Forbidden (CSRF cookie not set.): /accounts/login/
"POST /accounts/login/ HTTP/1.1" 403 2869

And I'm not getting print statements from CustomAuthenticationForm and CustomLoginView.

This is in my settings.py:

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',
]

I've tried commenting out 'django.middleware.csrf.CsrfViewMiddleware', from settings.py. I've tried adding csrf_exempt decorator to CustomLoginView:

@method_decorator(csrf_exempt, name='dispatch')
class CustomLoginView(LoginView):
    def post(self, request, *args, **kwargs):
        if request.method == 'POST':
            print("Received POST data:", request.POST)
            print("Received headers:", request.headers)
        return super().post(request, *args, **kwargs)
    
    
    authentication_form = CustomAuthenticationForm
    
    def get_success_url(self):
        print("get_success_url method......")
        return reverse_lazy('thread_list')

I've tried adding csrf_exempt to urls.py path.

I've even tried removing {% csrf_token %} from the login form. But I keep getting the same message.


Solution

  • Solved it.

    I've logged in via the website and the CustomLoginView returned the print statements:

    print("Received POST data:", request.POST)
    print("Received headers:", request.headers)
    

    Found out that the CSRF token was sent both in the data, as well as the headers.

    So I changed the code on the desktop app to this:

    url = "http://127.0.0.1:8000/accounts/login/"
    
            csrf_token = get_csrf_token()
    
            data = {
                "csrfmiddlewaretoken": csrf_token,
                "username": username, 
                "password": password,
                }
            
            headers = {
            'Cookie': f'csrftoken={csrf_token}',
            }
               
            response = requests.post(url, data=data, headers=headers)
    

    And it solved the issue.