Search code examples
pythondjangohttpclientsession-cookiescsrf

Django server 403 (CSRF token missing or incorrect)


I have a basic Django server with an python command line client for posting to this service. I have a login function and a post function. Even though the cookie for CSRF is being set from the login function the server is saying forbidden when I try to access the post_product endpoint after logging in.

I've been troubleshooting this for days and haven't had any luck.

/api/login/ function:

from django.views.decorators.csrf import csrf_exempt
from django.contrib.auth import authenticate, login, logout
from django.http import HttpResponse

@csrf_exempt
def handle_login(request):
    if request.method == 'POST':
        username = request.POST.get('username')
        password = request.POST.get('password')
        user = authenticate(request, username=username, password=password)

        if user is not None:
            if user.is_active:
                login(request, user)
                if user.is_authenticated:
                    return HttpResponse(author.name + ' is logged in, Welcome!', status=201, content_type='text/plain')
                return HttpResponse(data)

            else:
                return HttpResponse('disabled account', status=400, content_type='text/plain')

        else:
            return HttpResponse('invalid login', status=400, content_type='text/plain')

    else:
        return HttpResponse('request method invalid ' + request.method, status=400, content_type='text/plain')


/api/postproduct/ function:

def post_story(request):
    if request.method == 'POST' and request.user.is_authenticated:
        # Pull product details from request.
        # Validate product details.
        # Create model and save.

Python terminal client

FAILURE_MESSAGE = "The server responded with an unsuccessful code: "


def run():
    url ='http://127.0.0.1:8000' # For debugging
    logged_in = false
    with requests.session() as session:
        while True:
            command = input("""Enter one of the following commands:
            login 
            post \n""")

            # login case.
            if command == "login":
                url = user_inputs[1]
                logged_in = login(session, url)
                continue

            # post case.
            elif command == "post" and logged_in:

                post(session, url)                    
                continue

            else:
                print('incorrect command')
                continue


def login(session, url):
    username = input("Enter your username: \n")
    password = input("Enter your password: \n")
    response = session.post(url + "/api/login/", data={'username': username, 'password': password})

    # If any response but success notify user.
    if response.status_code != 201:
        print(FAILURE_MESSAGE + str(response.status_code))
        return False
    else:
        print("Successfully logged in!")
        return True


def post(session, url):
    # Check session is authenticated
    if 'csrftoken' not in session.cookies:
        print("Not authenticated, have you logged in to a service?")
        return

    # Omitted : prompt user for productname, category, price and details.

data = {'productname': productname, 'category': category, 'price': price, 'details': details}
    data_json = json.dumps(data)
    payload = {'json_payload': data_json}
    if not session_is_active():
        print("You aren't logged into any services")
        return
    else:
        response = session.post(url + "/api/postproduct/", data=payload)
        print(6)
        if response.status_code != 201:
            print(FAILURE_MESSAGE + str(response.status_code))
            return
        print("Post was successful")

When I run the client, login works fine and on inspection does set the csrf cookie. However when I then try and post the server responds with 403 forbidden. From the server's output:

[15/Aug/2019 15:45:23] "POST /api/login/ HTTP/1.1" 201 42
Forbidden (CSRF token missing or incorrect.): /api/postproduct/

Solution

  • Django's CSRF protection requires that you post the CSRF cookie and a token hidden in a form field. For AJAX requests, you can set a header instead of the form field.

    Try something like the following (untested):

    headers = {'X-CSRFToken': session.cookies['csrftoken']}
    response = session.post(url + "/api/postproduct/", data=payload, headers=headers)