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/
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)
Error = csrf Forbidden (CSRF token missing or incorrect.) when submitting a request to a Django backend:
in a form, include {% csrf_token %}, which generates an input tag with the csrf token value,
and in the request, have the headers include ‘X-CSRFTOKEN
headers: {
content_type: 'application/json',
'X-CSRFToken': "{{ csrf_token }}"
},
Related
I have been trying for some time now to use a login form, create a JWT with Flask-JWT-Extended and then pass it along with a redirect if it passes the checks.
Very simple example below, but I'm getting "No Authorization Header" in return, which makes me think it won't pass the header which is set? I want to protect a view which will render a template if the auth is ok.
#app.route("/")
def index():
return render_template("index.html")
#app.route('/login', methods=['POST'])
def login():
username = request.form["username"]
password = request.form["password"]
if not username:
return jsonify({"msg": "Missing username parameter"}), 400
if not password:
return jsonify({"msg": "Missing password parameter"}), 400
if username != 'test' or password != 'test!':
return jsonify({"msg": "Bad username or password"}), 401
access_token = create_access_token(identity=username)
response = redirect('http://127.0.0.1:5000/protected')
response.headers["Authorization"] = f'Bearer {access_token}'
return response
#app.route('/protected', methods=['GET'])
#jwt_required
def protected():
current_user = get_jwt_identity()
return jsonify(logged_in_as=current_user), 200
I'm having problems to do test on Django. I've been reading the documentation of the responses and I can't do the same as they explain on the documentation.
When I get the response, I only have access to response.status_code and can't access to context or redirect_chain when I write response.(and now PyCharm shows all available options).
I've checked on settings.py and I've 'BACKEND': 'django.template.backends.django.DjangoTemplates' to be sure that I'm using Django templates so I don't know why don't work the test. I need configure something?
The code of the test I'm trying to do it's:
from django.test import TestCase
from django.test.client import Client
class Test(TestCase):
def testLogin(self):
client = Client()
headers = {'X-OpenAM-Username': 'user', 'X-OpenAM-Password': 'password', 'Content-Type': 'application/json'}
data = {}
response = self.client.post('/login/', headers=headers, data=data, secure=True, follow=True)
assert (response.status_code == 200)
# self.assertRedirects(response, '/menu/', status_code=301, target_status_code=200)
I'm not using Django authentication, the login form sends the data to an IDP and if the IDP sends with a correct answer, the "login" it's successful:
def login(request):
logout(request)
message = None
if request.method == "POST":
form = LoginForm(request.POST)
if form.is_valid():
username = request.POST['username']
password = request.POST['password']
headers = {'X-OpenAM-Username': username, 'X-OpenAM-Password': password, 'Content-Type': 'application/json'}
data = {}
req = requests.post('http://openam.idp.com:8090/openamIDP/json/authenticate', headers=headers, params=data)
if req.status_code == 200:
respJson = json.loads(req.content)
tokenIdJson = respJson['tokenId']
request.session['tokenId'] = tokenIdJson
return render_to_response('menu/menu.html', request)
elif req.status_code == 401:
message = "Invalid username and/or password. Please, try again"
else:
form = LoginForm()
return render_to_response('registration/login.html', {'message': message, 'form': form},
context_instance=RequestContext(request))
The redirect assert it's commented because now it fails, when I do the debug I see an empty redirect_chain. I don't understand why happens this because running the web everything works, all views redirect as expected.
Why I only can check status_code? I'm doing something wrong when I redirect after a successful login that on a normal use it works but on the test not?
Thanks.
The remote authentication url expects the credentials as headers, but your local login view expects them as POST data. Your test passes the credentials as headers to your local view.
As a result, the form is passed an empty dictionary (request.POST contains no actual data), and the form is invalid. You get an empty form as a response, without any redirects.
You have to simply pass the credentials as post data to your local view:
def testLogin(self):
client = Client()
data = {'username': 'user', 'password': 'password'}
response = self.client.post('/login/', data=data, secure=True, follow=True)
assert (response.status_code == 200)
self.assertRedirects(response, '/menu/', status_code=301, target_status_code=200)
When I try to perform a over ajax login ('/login') with user and password, it does loggin, but when I try to get the user data after login current_user is Anonymous.
I have 3 routes:
/login: POST parameters user and password. The router takes the user and password and does the login.
/user: RETURN user data (name, email) if the user is logged in else 404
/logout: GET performs a logout
/user and /logout wont work cause the user is not logged in when I $.get this routes.
API routes (login, user, logout) are on 'localhost:5000' while front-end ReactJS is on 'localhost:3000'.
My user loader:
#login_manager.user_loader
def load_user(user_id):
try:
return User.query.get(user_id)
except:
return None
/login route:
#view.route('/login', methods=['POST'])
def login():
user = User.query.filter_by(email=request.form['email']).first()
if user and check_password_hash(
user.password, request.form['password']):
print(login_user(user, remember=True))
response = jsonify(user.serialize)
response.headers.add('Access-Control-Allow-Origin', '*')
return response
else:
return 404
/user route:
#view.route('/user', methods=['GET'])
def get_user():
if current_user.is_authenticated:
response = jsonify(User.query.get(current_user.id).serialize)
response.headers.add('Access-Control-Allow-Origin', '*')
return response
else:
abort(404)
In /user after I logged in current_user is AnnonymousUserMixin.
What am I missing here?
I'm using flask-oauthlib module to develop both oauth 2 client and provider
When using resource owner password flow, the provider won't redirect to client's redirect url.
Here is my client code for sending post to provider:
#app.route('/signin', methods=['POST', 'GET'])
def signin():
if request.method == 'POST':
username = request.form.get('username')
password = request.form.get('password')
f = {'client_id': 'jCce40zAaHXLxP0prU*************',
'client_secret': 'vzf7U219hrAjIYN70NcFo3VBQzott******',
'grant_type': 'password', 'scope': 'email',
'redirect_uri': 'http://localhost:8000/authorized', 'response_type': 'token'}
data = {'username': username, 'password': password}
encoded_url = 'http://127.0.0.1:5000/oauth/authorize?' + parse.urlencode(f)
headers = {"Content-Type": "application/json"}
requests.post(encoded_url, data=json.dumps(data), headers=headers)
return render_template('signin.html')
And here is provider authorize_handler
#app.route('/oauth/authorize', methods=['GET', 'POST'])
#oauth.authorize_handler
def authorize(*args, **kwargs):
if request.method == 'POST':
details = json.loads(request.data)
username = details['username']
password = details['password']
user = User.query.filter_by(user_name=username).first()
if user:
if user.check_password(password):
session['id'] = user.id
return True
return False
return False
if request.method == 'GET':
user = current_user()
if not user:
session['redirect_after_login'] = request.url
return redirect('/home')
client_id = kwargs.get('client_id')
client = Client.query.filter_by(client_id=client_id).first()
kwargs['client'] = client
kwargs['user'] = user
return render_template('authorize.html', **kwargs)
confirm = request.form.get('confirm', 'no')
return confirm == 'yes'
Also Flask-oauthlib oauth 2 provider logging
Fetched credentials from request {'response_type': 'token', 'state': None, 'client_id': 'jCce40zAaHXLxP0prU************', 'redirect_uri': 'http://localhost:8000/authorized'}.
Found redirect_uri http://localhost:8000/authorized.
Validate client 'jCce40zAaHXLxP0prU***********'
Save bearer token {'scope': 'email', 'access_token': 'y08hkm594YbLe2*****', 'expires_in': 180, 'token_type': 'Bearer'}
Authorization successful.
127.0.0.1 - - [20/Sep/2015 17:40:53] "POST /oauth/authorize?client_id=jCce40zAaHXLxP0prU*********&client_secret=vzf7U219hrAjIYN70NcFo3VBQzot**********&response_type=token&grant_type=password&scope=email&redirect_uri=http%3A%2F%2Flocalhost%3A8000%2Fauthorized HTTP/1.1" 302 -
The way I see it, the token is being saved but:-
Redirection does not occur
It cause the client to load like forever until I restart it (Even if I tried to access other routes, the client does not respond)
What am I missing ?
NB:
I've implemented server side flow and client side flow and they worked fine
I'm still new to flask
I think you are mixing different grant types of OAuth2. With the Resource Owner Password Credentials grant, the authorization server does not do a redirect, instead it provides a token response to the client.
https://www.rfc-editor.org/rfc/rfc6749#section-4.3
redirect_uris are associated with the Authorization Code grant.
It seems like in Flask, cookies are set by modifying the response object directly.
How can I return a response object, but also redirect a user to a different page upon successful login? I'd like to specifically redirect the user instead of rendering a different page, in case the user hits REFRESH.
Here's my current code, which simply displays the same page, login.html:
#app.route('/login', methods=['POST', 'GET'])
def login():
errors = []
if request.method == 'POST':
email = request.form['email']
password = request.form['password']
#Check the user's e-mail
try:
u = User(email)
except UserError, e:
errors.append(e)
else:
#Check the user's password
if not u.authenticatePassword(password):
errors.append(('password','Invalid password'))
return render_template('login.html',error=errors)
#Set the session
s = Session()
s.user_id = u.user_id
s.ip = request.remote_addr
#Try to set the cookie
if s.setSession():
response = make_response( render_template('login.html',error=errors))
response.set_cookie('session_id', s.session_id)
return response
return render_template('login.html',error=errors)
You should change your code to something like:
from flask import make_response
if s.setSession():
response = make_response(redirect('/home'))
response.set_cookie('session_id', s.session_id)
return response