3

I have a Django view where when a user performs a certain task, an external Python microservice retrieves and then sends some data to the Django view, which is supposed to show it to the user on the template. To send the data i'm using Python requests, the problem is that the Json response is being refused by Django for two reasons: 1) The CSRF Token is not set 2) The view is @login_required

Here is my view:

@login_required
def myTestView(request):    

    if request.method == 'POST':
        received_json_data=json.loads(request.body)
        print(received_json_data)
        print('received.')

And here is how i send the response:

import requests

req = requests.post('http://127.0.0.1:8000/myTestView/', json={"test": "json-test"})
print('SENT')

With the actual code, i get this error from Django:

Forbidden (CSRF cookie not set.): /myTestView/
[2019-12-24 15:36:08,574] log: WARNING - Forbidden (CSRF cookie not set.): /myTestView/

I know i can use @csrf_exempt, but since the data that i'm sending is personal, i would like it to be as safe as possible, so i need to find a way to send the CSRF Token. The second thing i need to do, is how do i "login" using the request?

Jack022
  • 867
  • 6
  • 30
  • 91
  • This [answer](https://stackoverflow.com/questions/13567507/passing-csrftoken-with-python-requests) explains it. You need to use python requests' session. – postoronnim Dec 24 '19 at 16:22
  • Does this answer your question? [Passing csrftoken with python Requests](https://stackoverflow.com/questions/13567507/passing-csrftoken-with-python-requests) – Nazim Kerimbekov Dec 24 '19 at 16:24

1 Answers1

3

I like this question, so I'll try to describe the whole process in detail.

  1. Server side.

First step is to get csrf_token which you'll use in the further post request.

After that you have to authenticate the session.

So let's write a view to serve get for getting csrf_token and post for authenticating session. The same idea for protected view.

After we get authenticated it is possible to access protected view.

import json

from django.contrib.auth import authenticate, login
from django.contrib.auth.decorators import login_required
from django.http import HttpResponse, HttpResponseForbidden
from django.middleware.csrf import get_token


@login_required
def myTestView(request):
    if request.method == 'POST':
        data = request.POST.get('data')
        print(json.loads(data))
        print('received.')

    response = HttpResponse(get_token(request))
    return response


def login_view(request):
    if request.method == 'POST':
        username = request.POST['username']
        password = request.POST['password']
        user = authenticate(request, username=username, password=password)

        if user is not None:
            login(request, user)
            return HttpResponse('authenticated')
        else:
            return HttpResponseForbidden('wrong username or password')

    response = HttpResponse(get_token(request))
    return response

  1. Client side.

requests.post does not fit for this task because it can't track (or it's really hard) request cookies and headers.

But you can use for this purpose requests.session()

Django is not able to process csrf_token in json, that's why you have to pass json in request data.

import json

import requests


session = requests.session()
token = session.get('http://127.0.0.1:8000/login/')

session.post('http://127.0.0.1:8000/login/',
             data={
                 'username': '<username>',
                 'password': '<password>',
                 'csrfmiddlewaretoken': token})

token = session.get('http://127.0.0.1:8000/myTestView/')
data = json.dumps({'test': 'value'})
session.post('http://127.0.0.1:8000/myTestView/',
             data={
                 'csrfmiddlewaretoken': token,
                 'data': data})

Mind adding urls.py for the views

I checked this code and it is working well. If anyone has an ideas how to improve it I will love to update it.

artembo
  • 640
  • 6
  • 13
  • Hey! Thanks for your answer! I'm testing it right now. One thing i want to understand:so the login view you added will only be used to authenticate requests coming from the external script i'm mentioning? I'm asking because to handle authentication on the site, i'm using a whole different view provided by Django-Allauth – Jack022 Dec 24 '19 at 18:50
  • Another thing, after 'session.post', what view do you mean with /view/? – Jack022 Dec 24 '19 at 18:57
  • Ok, i think i'm getting the hang of it! I'm able to log the user in, but i still get 'CSRF Token missing or incorrect'! – Jack022 Dec 24 '19 at 19:08
  • I corrected the answer, view/ was replaced by myTestView/ by mistake – artembo Dec 24 '19 at 19:09
  • What about login view. I added it because you did not provide the way you actually authenticate the user. I think you can do the same trick with Django-Allauth auth view. – artembo Dec 24 '19 at 19:13
  • I'm sorry! The CSRF token was not incorrect, it was a typo error on my side! – Jack022 Dec 24 '19 at 19:14
  • Sorry again! Your answer works flawlessly, i'm now going through it to understand everything step by step. One last thing, but i think it belongs to another question: in this case, i'm editing the username and password manually. But in my site, i'll have more users, so whenever that service is called, the user and password fields should be edited to match the credentials of the user who is using the service. Is there any way to do it? Thanks again and merry Christmas! – Jack022 Dec 24 '19 at 19:18
  • Merry Christmas and you're welcome! Sorry, but i didn't get the question... The username and password must match the credentials of a user instance of the project User model. The way you are providing it can vary on the implementation. This way you can authenticate any of your user. If you provide more details in the other question i'll take a look at it. – artembo Dec 24 '19 at 19:28
  • Ok, sorry if i wasn't very clear. I mean, suppose that any other user is using the service, on the fields 'username' and 'passwords' there should be the credentials of that users, What i mean, is how can i pass those credentials to the external script? – Jack022 Dec 24 '19 at 20:53
  • Okay, do you mean how to pass username and password inside client script without modifying code for each user? If I get you right i can suggest two ways. 1. Environment variables. The running script will be like `USERNAME= PASSWORD= python client-script.py` and to get this variables in code like `username = os.environ.get('USERNAME')` and the same for password. 2. Argparse `python client-script.py --username= --password=` I think this way is better for usability. Find examples, it's easy. Anyway both ways are very popular for passing data into code. – artembo Dec 24 '19 at 21:09
  • yeah, only problem here is that the external script should have the credentials of the user using the service, and i don't understand how to pass it to that script. I'll see what i can find, and in case i have other problems i'll create another question. Thanks again :) – Jack022 Dec 24 '19 at 21:13
  • Opened a bounty to reward this answer, just need to wait 23 hours :) – Jack022 Dec 27 '19 at 15:25
  • Thanks a lot and Happy New Year! – artembo Dec 31 '19 at 18:57