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?
I like this question, so I'll try to describe the whole process in detail.
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
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.
Related
I'm building a flask application and am trying send post requests in the terminal using the requests library, but it is giving a 400 error, but I don't know what's malformed about the request.
Here's the signup page I'm trying to reach in my browser:
And the subsequent result after submitting the post:
However, when I try and recreate this post request and use the same info in the command line I get a 400 status code.
Here's the underlying code I use to generate it:
FORM:
class SignupForm(FlaskForm):
"""Main form for sign up page"""
email = EmailField(label = 'email', validators = [DataRequired(), Email()])
password = PasswordField(label = 'password',
validators = [DataRequired(), Length(min=9, max=20),
check_valid_password])
sec_question = SelectField(label = 'Security Question',
validators = [DataRequired()],
choices = ['What is your favorite band?',
'What is your favorite pets name?',
'What was the last name of your favorite childhoold teacher?',
'In what city were you born?',
'What is your best friends last name?',
'What is the country you would most like to visit?'])
sec_answer = TextField(label='your answer. not case sensitive', validators=[DataRequired(), Length(4, 200)])
Here's the flask code that handles the incoming request:
#user.route('/signup', methods=['GET', 'POST'])
def signup():
form = SignupForm()
if form.validate_on_submit():
user_exists = User.query.filter_by(email = request.form.get('email')).first()
if user_exists:
flash('User already exists with that e-mail. Please login instead', 'warning')
return redirect(url_for('user.signup', form = form))
user = User()
form.populate_obj(user)
user.password = User.encrypt_password(request.form.get('password'))
user.save()
# send confirmation e-mail using celery
from app.blueprints.main.tasks import send_email
token = generate_new_account_token(form.email.data)
send_email.delay(
subject = 'Welcome to CPM! Please confirm your account',
recipients = [form.email.data],
template = 'email/user_new_confirm.html',
confirm_url = url_for('user.confirm_new', token = token, _external=True)
)
flash(f'A confirmation e-mail has been sent to {form.email.data}', 'info')
redirect(url_for('user.signup', form=form))
return render_template('blueprints/user/signup.html', form=form)
I can't for the life of me seem to get this to work inside a post request. It's running on localhost:5000 on my computer.
Here are two examples that illustrate the issue. My successful GET request:
My unsuccessful POST request:
data = {'email': 'myemail#gmail.com', 'password': 'Passw0rd!', 'sec_question': 'What is your favorite band?', 'sec_answer': 'paul simon'}
requests.post('http://localhost:5000/signup', data = data)
As a security measure, forms need a CSRF token to be sent along with the POST request to prevent CSRF (Cross-Site Request Forgery, sometimes abbreviated XSRF) attacks. Browsers will handle that for you, but in cURL you will have to retrieve and use a CSRF token manually (which is usually a pain in the neck).
For development purposes, you can temporarily disable the CSRF protection. Here is an explanation on how to do that with Flask.
Essentially, just add the #csrf.exempt decorator to your signup method:
from flask_wtf import csrf
#user.route('/signup', methods=['GET', 'POST'])
#csrf.exempt
def signup():
# The rest of the method
Alternatively, you can quickly and easily disable CSRF for all your views by setting WTF_CSRF_CHECK_DEFAULT to False.
WARNING: Never push a form view to production with CSRF protection disabled.
I need to make a request to b.com/rest/foo to get json data for my application. I want to do it this way to protect the credentials rather than expose them on every page.
I found Consume an API in Django REST, server side, and serve it ,client side, in Angular and the corresponding answer at https://stackoverflow.com/a/65672890/2193381 to be a great starting point.
I created a local url to replicate the data that the external server will return and then tried the following
import requests
from django.http import JsonResponse
from django.contrib.auth.decorators import login_required
from django.views.decorators.cache import never_cache
#never_cache
#login_required
def fakedata(request, item):
return JsonResponse({'item': item})
def getdata(request, item):
url = f"http://localhost:8080/rest/{item}"
username = os.getenv('SITE_USERNAME', None)
password = os.getenv('SITE_PASSWORD', None)
userpass = dict(username=username, password=password)
data = requests.get(
url,
auth=requests.auth.HTTPBasicAuth(**userpass),
)
if data is not None and data.status_code == 200:
try:
return JsonResponse(data.json(), safe=False)
except ValueError:
print("!JSON")
return JsonResponse({})
print("!data")
return JsonResponse({})
urlpatterns = [
path('rest/<str:item>', fakedata),
path('foo/<str:item>', getdata),
]
When I test it with
python manage.py runserver 8080
and call http://localhost:8080/foo/a, I am getting back the html of the login page and not the data from http://localhost:8080/rest/a that I am expecting.
What changes do I need to make to get it to work?
I just went through Django documentation and found this useful and working.
you can first authenticate the user by using the authenticate() method then login with request and call your fakedata() function with passing the request:
from django.contrib.auth import authenticate, login
user = authenticate(request, **userpass)
if user is not None:
login(request, user)
data = fakedata(request, item)
else:
pass
I am currently working on a project with Django. I am trying to implement the ability of login and logout a user from the application using only python scripts in order to send post request from the client to the server.
I am trying to logout a user from my application but it does not seems to work. In my login function this is the method used to login a user:
# Function for user's login
#csrf_exempt
def loginUser(request):
login_user = authenticate(username=user.customer_username, password=user.customer_password)
if login_user is not None:
if login_user.is_active:
request.session.set_expiry(86400)
login(request, login_user)
print(request.user.is_active)
http_response = HttpResponse()
return http_response
The result of the print is True here which means that the login method is correct if I am not wrong. When I try to logout the user, using this method:
# Function for user's logout
#csrf_exempt
def logoutUser(request):
# Loging out the user
print(request.user.is_active)
logout(request)
http_response = HttpResponse()
return http_response
It does not logout the user and the result of the print is False here, which means that the user is not logged in. If anyone have any idea, how to solve this, it would be very helpful. Thanks.
I managed to find the answer for this problem. Because I was using a python script to send and receive post requests between the server and the client, the current user which was logged in was not saved due to after the request from the client the connection between client and server stoped. So by initialising a global Session object and later use that object to make request it allowed me to persist certain parameters across requests.
This is the code I used in the client side in order to make requests:
import requests
from requests import Session
# Create the Json object for the login
data = {'username': username,
'password': password}
r = requests.Session()
login_response = r.post("http://127.0.0.1:8000/api/login", data=data)
logout_response = r.post("http://127.0.0.1:8000/api/logout")
And this is the code I used in the server side:
# Function for user's login
#csrf_exempt
def loginUser(request):
# Get the username and password from the post request
username = request.POST['username']
password = request.POST['password']
login_user = authenticate(username=username,
password=password)
if login_user is not None:
if login_user.is_active:
login(request, login_user)
http_response = HttpResponse()
return http_response
# Function for user's logout
#csrf_exempt
def logoutUser(request):
# Loging out the user
logout(request)
http_response = HttpResponse()
return http_response
Thanks for the help!!
Commenting for visibility, Kyriakos, this is completely correct.
I also am trying to query Django by logging in and out using Django Auth (django.contrib.auth) but using a custom python script to test the posts of API's when logging in. Without the global request sessions the django.contrib.auth login will always show Anonymous User when attempting to use request.user, and this is the culprit.
import requests
from requests import Session
r = requests.Session() #SETTING GLOBAL SESSION FOR DJANGO TO READ MY SESSION
x = r.post('http://127.0.0.1:8000/newsagency/api/login/', {'username':'jake', 'password':'UOL'})
print("login response: ", x.text)
x = r.post('http://127.0.0.1:8000/newsagency/api/poststory/', {'headline' :'Story 1',
'category' : 'pol',
'region' : 'w',
'details' : 'some details'})
print("post story response: ", x.text)
def LogIn(request):
if request.method=="POST":
loginname = request.POST["name"]
loginemail = request.POST['Email']
loginpassword = request.POST["Password"]
user = authenticate(username=loginname, email=loginemail, password=loginpassword)
#login_required()
def signout(request):
logout(request)
return redirect(index)
My website uses Django's default auth module to do user authentications. Usually, user just needs to fill out a html form in the login page with his/her username and password and click submit, then the CSRF-protected data will be posted to /auth/login/, the Django auth endpoint.
But now for some reason I also need to do this on my server. This is the same server as the backend authentication server. After researching and trials and errors, I finally got:
from django.views.generic import View
from django.utils.decorators import method_decorator
from django.views.decorators.csrf import csrf_protect
import requests
class fb_login(View):
"login view for Facebook OAuth"
#method_decorator(csrf_protect)
def get(self, request):
''' code that gets username and password info. from user's FB profile
is not shown here '''
# login code
url = 'http://localhost:8000/auth/login/'
client = requests.session()
# Retrieve the CSRF token first
client.get(url) # sets cookie
csrftoken = client.cookies['csrftoken']
form = {'username': uname, 'password': pw}
header = {'X-CSRFToken': csrftoken}
resp = requests.post(url, data=form, headers=header)
I also tried to add csrf token as part of the form field:
form = {'username': uname, 'password': pw , 'csrfmiddlewaretoken': csrftoken}
resp = requests.post(url, data=form)
I pretty much just followed the Django doc. But I still get the 403 error saying CSRF verification failed, CSRF cookie not set. I wonder if I missed something here? I've double-checked the process against the Django doc but I cannot find anything that might be wrong or missing.
As suggested by #rodrigo, cookies are also needed to pass the CSRF security check. Here's a simple code snippet:
csrftoken = requests.get(url).cookies['csrftoken']
form = {'username': uname, 'password': pw}
header = {'X-CSRFToken': csrftoken}
cookies = {'csrftoken': csrftoken}
resp = requests.post(url, data=form, headers=header, cookies=cookies)
Maybe a stupid question here:
Is Requests(A python HTTP lib) support Django 1.4 ?
I use Requests follow the Official Quick Start like below:
requests.get('http://127.0.0.1:8000/getAllTracks', auth=('myUser', 'myPass'))
but i never get authentication right.(Of course i've checked the url, username, password again and again.)
The above url 'http://127.0.0.1:8000/getAllTracks' matches an url pattern of the url.py of a Django project, and that url pattern's callback is the 'getAllTracks' view of a Django app.
If i comment out the authentication code of the 'getAllTracks' view, then the above code works OK, but if i add those authentication code back for the view, then the above code never get authenticated right.
The authentication code of the view is actually very simple, exactly like below (The second line):
def getAllTracks(request):
if request.user.is_authenticated():
tracks = Tracks.objects.all()
if tracks:
# Do sth. here
Which means if i delete the above second line(with some indents adjustments of course), then the requests.get() operation do the right thing for me, but if not(keep the second line), then it never get it right.
Any help would be appreciated.
In Django authentication works in following way:
There is a SessionMiddleware and AuthenticationMiddleware. The process_request() of both these classes is called before any view is called.
SessionMiddleware uses cookies at a lower level. It checks for a cookie named sessionid and try to associate this cookie with a user.
AuthenticationMiddleware checks if this cookie is associated with an user then sets request.user as that corresponding user. If the cookie sessionid is not found or can't be associated with any user, then request.user is set to an instance of AnonymousUser().
Since Http is a stateless protocol, django maintains session for a particular user using these two middlewares and using cookies at a lower level.
Coming to the code, so that requests can work with django.
You must first call the view where you authenticate and login the user. The response from this view will contain sessionid in cookies.
You should use this cookie and send it in the next request so that django can authenticate this particular user and so that your request.user.is_authenticated() passes.
from django.contrib.auth import authenticate, login
def login_user(request):
user = authenticate(username=request.POST.get('username'), password=request.POST.get('password'))
if user:
login(request, user)
return HttpResponse("Logged In")
return HttpResponse("Not Logged In")
def getAllTracks(request):
if request.user.is_authenticated():
return HttpResponse("Authenticated user")
return HttpResponse("Non Authenticated user")
Making the requests:
import requests
resp = requests.post('http://127.0.0.1:8000/login/', {'username': 'akshar', 'password': 'abc'})
print resp.status_code
200 #output
print resp.content
'Logged In' #output
cookies = dict(sessionid=resp.cookies.get('sessionid'))
print cookies
{'sessionid': '1fe38ea7b22b4d4f8d1b391e1ea816c0'} #output
response_two = requests.get('http://127.0.0.1:8000/getAllTracks/', cookies=cookies)
Notice that we pass cookies using cookies keyword argument
print response_two.status_code
200 #output
print response_two.content
'Authenticated user' #output
So, our request.user.is_authenticated() worked properly.
response_three = requests.get('http://127.0.0.1:8000/hogwarts/getAllTracks/')
Notice we do not pass the cookies here.
print response_three.content
'Non Authenticated user' #output
I guess, auth keyword for Requests enables HTTP Basic authentication which is not what is used in Django. You should make a POST request to login url of your project with username and password provided in POST data, after that your Requests instance will receive a session cookie with saved authentication data and will be able to do successful requests to auth-protected views.
Might be easier for you to just set a cookie on initial authentication, pass that back to the client, and then for future requests expect the client to send back that token in the headers, like so:
r = requests.post('http://127.0.0.1:8000', auth=(UN, PW))
self.token = r.cookies['token']
self.headers = {'token': token}
and then in further calls you could, assuming you're in the same class, just do:
r = requests.post('http://127.0.0.1:8000/getAllTracks', headers=self.headers)