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.
Related
I am having some problems on the authentication part for my Django app, using a CustomUser.
The logic is the following: I need to send credentials (email/password) to an external API, from which I retrieve the access token which will be used on the later requests. During the process, I also create (or update, if it's already there) a CustomUser inside my local db, with the same credentials as in the external database. Then I try to authenticate the user in my app with the same credentials.
Below the relevant parts of code:
models.py:
class CustomUser(AbstractUser):
email = models.EmailField("Email", max_length=255, unique=True, null=True)
custom_user_id = models.IntegerField("Custom user id", unique=True, null=True)
name = models.CharField("Name", max_length=255, null=True)
initials = models.CharField("Initials", max_length=255, null=True)
views.py
from django.contrib.auth import login as auth_login
#api_view(['POST'])
#never_cache
def user_login(request):
''' User login '''
if request.method == 'POST':
url = "THE EXTERNAL API"
payload = {
'email':request.data['email'],
'password':request.data['password']
}
headers = {
'Origin': 'REDACTED',
'Content-Type': 'text/plain'
}
email = request.data['email']
username = email
password = request.data['password']
payload = '{"email":"' + email + ',"password":"' + password + '}'
r = requests.post(url, data = payload, headers=headers)
if r.status_code == 200:
data = r.json()
# Get user // create one if it doesn't exist yet
user, created = CustomUser.objects.update_or_create(
custom_user_id = data['data']['uid'],
defaults = {
'username': username,
'initials': data['data']['initials'],
'email': data['data']['email'],
'name': data['data']['name']
})
#trying to login user
auth_login(request, user)
request.session['access_token'] = data['data']['access_token']
First all, this code works perfectly fine when I run the app in local - in fact I can see that request.user correctly stores the logged user inside any other views, and the #login_required decorator works as expected.
The problems arise when I run the same app on my deployed version (aws ec2 with nginx). In this case, 'request.user' contains the correct user only inside this login view. But if I print request.user in any other view, it actually returns AnonymousUser, as if the auth_login function has actually failed.
As a result, the #login_required decorathor no longer works properly (because request.user does not contain the authenticated user). Strangely, its counterpart for class-based view (LoginRequiredMixin) works perfectly fine instead (not sure if they look at different things?), however I have a mix of function-based and class-based views in my app and I can't convert everything into class-based view (also I'd like to solve the issue at the root).
Another strange thing is, despite the fact that request.user contains AnonymousUser, request.session contains the right credentials of the user (for example I'm able to retrieve the ID of the logged user with request.session['_auth_user_id']
If the authentication has failed, then why request.session correctly contains the information about the logged user?
I also tried the following, based on the answer on a similar question on SO:
from django.contrib.auth import authenticate
user = authenticate(username=username, password=password)
However it didn't solve anything.
I'm integrating stripe into my Django web application platform. When sellers onboard with Stripe Connect, an Account is created for them, and an AccountLink is created with a specified return_url and refresh_url that Stripe uses to redirect back to my web application. However, when the sellers are redirected back to my web application they are no longer logged into the web application. Is there a better solution than forcing the user to log back in?
The following are some code snippets:
views.py
def stripe_on_boarding(request):
if request.method == "GET":
stripe_sk = settings.STRIPE_SECRET_KEY
stripe.api_key = stripe_sk
user = request.user
account = None
print("I'm logged in as and am onboarding stripe [" + user.first_name + "]")
if user.stripe_account is None:
account = stripe.Account.create(type='express')
user.stripe_account = account.id
user.save()
print("Saved User Object!")
else:
account = stripe.Account.retrieve(user.stripe_account)
if account is not None and not account.details_submitted:
account_links = stripe.AccountLink.create(
account=account.id,
refresh_url='http://098c818fbf8a.ngrok.io/marketplace/stripe_on_boarding_refresh',
return_url='http://098c818fbf8a.ngrok.io/marketplace/seller_return',
type='account_onboarding',
)
return redirect(account_links.url)
return render(request, 'become_seller_part_1.html', {'msg' : "You've already registered with stripe.", 'is_registered' : True})
# TODO: render a page if this view was triggered by a method other than GET.
Could this be happening because I'm using ngrok to redirect to my localhost?
It turns out that it is because I'm using ngrok. You must use the same URL throughout a session; that is, you can not login as a user on localhost and maintain the session when being redirected to the ngrok URL.
The solution is to use the ngrok URL when logging in as a user.
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.
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)
I am sending out a confirmation token to users after they register to confirm their email. The issue I have is that I cant get to the confirm method as it requires the user to login and once they go to the login method I cant work out how to direct them back to the confirm method.
Note the code is heavily based on the (very good) book 'Flask Web Development' by Grinberg 2014.
We start here with a new user signing up:
#auth.route('/signup', methods=['GET', 'POST'])
def signup():
#validate form and create user
...
token = user.generate_confirmation_token()
send_email(user.email, 'Please Confirm Email Address',
'email/confirm', user=user, token=token)
return redirect(url_for('auth.login'))
return render_template('auth/register.html', form=form)
The new user is sent and email, when the user clicks on the link in the email they are sent to this route:
#auth.route('/confirm/<token>')
#login_required
def confirm(token):
if current_user.confirmed:
return redirect(url_for('main.index'))
if current_user.confirm(token):
#do something here
else:
#message
return redirect(url_for('main.index'))
As login is required to get to the confirm end point, the user is directed here first:
#auth.route('/login', methods=['GET', 'POST'])
def login():
form = LoginForm()
if form.validate_on_submit():
user = User.query.filter_by(email=form.email.data).first()
if user is not None and user.verify_password(form.password.data):
login_user(user)
return redirect(request.args.get('next') or url_for('main.index'))
return render_template('auth/login.html', form=form)
The issue I have is that after the user logs in they are not redirected to the confirm route and I can't work out how to redirect to confirm for this case where the user is just logging in so that they can respond to the signup email link. Any ideas?
I'm also learning from this book. Though I don't know what's wrong with you, I can show some key information to you to solve your problem.
First, your test email have the URL like this.(http://127.0.0.1:5000/auth/confirm/eyJleHAiOjE0NjY0ODQwOTAsImFsZyI6IkhTMjU2IiwiaWF0IjoxNDY2NDgwNDkwfQ.eyJjb25maXJtIjo5OX0.npxbWrOYVzX2HYibnDQLNS0FuX-J9XB-TcmZZSPri-8)
Then, you click it and though the code #login_required, so you redirect to the login page, but the URL is not equal to the usual login URL. It has some thing different like this. (http://127.0.0.1:5000/auth/login?next=%2Fauth%2Fconfirm%2FeyJleHAiOjE0NjY0ODQwOTAsImFsZyI6IkhTMjU2IiwiaWF0IjoxNDY2NDgwNDkwfQ.eyJjb25maXJtIjo5OX0.npxbWrOYVzX2HYibnDQLNS0FuX-J9XB-TcmZZSPri-8)
Next, return redirect(request.args.get('next') or url_for('main.index')) this line of your code will get the next confirm URL after the key next=, and visit the confirm page.
So you can check your URL if it has some problems.
Additionally, I recommend you debug your code in pycharm because you can set breakpoint and track variables' changes. Good luck.