Use JWT-Extended to produce JSON Web Signatures for user confirmation - python

I am building a RESTful API for an upcoming project. This needs some kind of user account verification. I implemented a token based confirmation procedure previously using itsdangerous. But I wonder wether I can accomplish the same by using JWT-Extended, as its already part of my app and I want to keep the number of dependencies as low as possible.
Could I just use a normal access_token for that?
I appreciate your help!
Edit:
I implemented the following two methods and they seem to work. I am just not sure, if this is considered good practice.
#blueprint.route('/gen_confirmation_token', methods=['GET'])
#jwt_required
def gen_confirmation_token():
access_token = create_access_token(identity=get_jwt_identity(), user_claims={"confirm": True}, expires_delta=dt.timedelta(seconds=3600))
# TODO send a link to mail
return jsonify({"message": "confirmation token sent"}), 200
#blueprint.route('/confirm/<string:token>', methods=['GET'])
#jwt_required
def confirm_user(token):
user_identity = get_jwt_identity()
current_user = User.query.get(user_identity)
decoded_token = decode_token(token)
if decoded_token['identity'] == user_identity and decoded_token['user_claims'].get('confirm', False):
current_user.confirm()
return jsonify({"message": "user confirmed"}), 200
return jsonify({"message": "invalid confirmation token"}), 400

EDIT
Seeing the code you've added, which seems to be working, the nature of my answer changes. I think your solution to the problem would be considered good practice. The main problems you have are security, i.e. no one should be able to create their own token, which the hash value confirms, and the tokens should be personalised in such a way that one and only one person can use them, which the user identity guarantees.
Since you can encode as much information as you want in a JWT token, you should be able to store the information you need in it as well. I'm not sure what format you were thinking of, but if you were to, for example, store the confirmation step someone still has to reach, you can store something like the following:
#jwt.user_claims_loader
def add_confirmation_status_to_token(user):
"""Given an identity, add the confirmation status to the token"""
return dict(status=user.confirmed_email)
For more information, have a look here

Related

How to properly test an app with REST API + Flask JWT extended?

Assuming that I have an API endpoint, whose resources are accessible to authorised users only who possess a valid access token, similar with this:
from flask_restful import Resource
from flask_jwt_extended import jwt_required
class Collection(Resource):
#jwt_required
def get(self):
"""
GET response implementation here.
"""
# Queries and logic here
data = 10
if(is_everythig_ok()):
return {"data": data}, 200
else:
return {"message":"Failed to get data."}, 400
And assuming that there is a LoginUser endpoint which returns a valid access_token, how can I write some unit tests to reproduce the two status codes (200 for success and 400 for failure) while user HAS a valid access token AND also the case when the user DOES NOT have a valid access_token.
I have test my endpoints with POSTMAN and it seems ok, but I also need to write some unit tests for proof. So, what is the proper way of doing that?
Since this is an API, what you really want are integration tests. The way I do this, is like this:
create a test user
request a valid token
access a protected resource with the valid token
access the resource with an invalid token
any other tests making sure you cover every method in every controller.
remove the test user
You will end up with a lot of integration tests which you can automate, postman is great at this, you can build collections for every endpoint and run them easily.
More than this, you can start measuring how long each request takes to execute and can start looking at those which take too long.
Unit test the logic inside your methods, but not your authorization system, not your endpoints and not your controllers. Those you integration test.

Why find_one is returning all documents in my case?

I'm trying to use BCryptAuth to protect resource as well as for login system.
I'm trying to fetch only one document based on user's email entered at login page.
class BCryptAuth(BasicAuth):
def check_auth(self, email, password, allowed_roles, resource, method):
account = app.data.driver.db['users'].find_one({'email': email})
return account and \
bcrypt.hashpw(password.encode('utf-8'),account['salt'].encode('utf-8')) == account['password']
But when i try to access the users end point via postman, it actually authenticates but returns all documents. I'm bit confused. If my approach is wrong, pls provide me one.
The Auth class you mention will only allow or not the access to the API. It does nothing for resource filtering.
If you want resource filtering when getting users, you can create an event hook and make a pre-GET dynamic filter. Check the documentation, it should help.

Example of how to write Django Test

I have to write some tests for some services I build that connect our backend to a mobile app another team member is building. I was asked to write some unit tests once I finished them. I am not familiar with Django testing so I wanted to ask if someone could give me an example of how you would test one of the services. That way I can then learn by example and do the rest on my own?
This is one example of a service I built that finds if there is a user by that email in our database and return a json object:
#csrf_exempt
def user_find(request):
args = json.loads(request.body, object_hook=utils._datetime_decoder)
providedEmail = args['providedEmail']
try:
user = User.objects.get(email=providedEmail)
user_dict = {'exists': 'true', 'name': user.first_name, 'id': user.id}
return HttpResponse(json.dumps(user_dict))
except User.DoesNotExist:
user_dict = {'exists': 'false'}
return HttpResponse(json.dumps(user_dict))
What would be the correct way to test something like this? I am guessing I have to mimic a request somehow that gives me an email and then have two tests where one matches and one doesn't match an existing user and make sure each returns the appropriate object. Is this the correct way of thinking about it? Can someone help me out a bit with the syntax? I'm guessing using django.test.Client in some way would be appropriate?
What you need to test is if your code can not be broken. That means your code should always give a response and not generate any error in any case.
Since if the things you can test are-
1) authorization- is service accessible by any user or only authenticated ones, if only authenticated then test response for both kind of users, anonymous and authenticated. While testing response, test that you get 401 for anonymous users and 200 for authenticated ones.
2) response for non existing emails
3) response for users without a first_name if first_name is optional.
4) response data in case of valid request- is data received of correct user
There may be more tests, these from top of my head.

How to implement "Incorrect username/password" hint for a webservice using Flask HTTP Auth?

I have a client app that interacts with a web service to retrieve account information. There's a requirement that the user is notified if they mistyped the username/password. I'm modifying the web service to return something to my client to provide a hint to the user that there's an error in input.
How do I correctly implement the "username/password" not found for a web service using Python?
Do I tell the user that the username exists, but the password is incorrect?
Do I tell the user that there is no such username, but the password matched something?
Do I show a generic username/password combination is not found?
Do I use different status codes for different situations or provide a JSON payload with the error?
here's my code so far:
from flask.ext.httpauth import HTTPBasicAuth
accounts = [
["user0", "password0"],
["user1", "password1"],
]
#app.route('/accountlist')
#auth.login_required
def accountlist()
username = auth.username();
if ... : #check if accounts does not have the given username
#notify the sender that there is no such username
return Response('Not Authorized', 401, {'WWW-Authenticate': 'Basic'})
else:
#proceed to check password and retrieve/return account information
Do I show a generic username/password combination is not found?
Yes. Why do you think this is "generic"? Because it is the standard. This is the correct way because than a hacker can't go phishing for usernames.
Do I tell the user that the username exists, but the password is incorrect?
No, letting the user know that the username is correct is a user enumeration vulnerability. You are letting an attacker know which usernames are valid allowing them to narrow their target range. This would be useful if they later decided to try a brute force attack as they already know the usernames are correct and now they only need a working password.
Do I tell the user that there is no such username, but the password matched something?
Definitely not. This would mean that the attacker now had a valid password and could use any other username enumeration vulnerability on your site in order to try and find a valid username. Another common username enumeration location is the forgotten password form - many sites report back that there is no such username allowing an attacker to refine their list. Alternatively, they could use this password and brute force a username from it which may be a much easier job as usernames shouldn't benefit from being complex.
As an aside to this, you should be storing your passwords salted and hashed using a secure, slow algorithm such as bcrypt. This should mean it is not possible for you to practically check to see if any password matches the one entered.
Do I show a generic username/password combination is not found?
Yes!
Do I use different status codes for different situations or provide a JSON payload with the error?
Your JSON could return true or false to let the calling JavaScript know whether authentication was successful. If you ever develop any brute force protection, this should be accomplished by introducing a delay in the response rather than hard locking accounts. Hard locking accounts leads to a DoS attack as an attacker can lock out a valid account by repeatedly using the wrong password on purpose. For this reason, only a true/false response is really needed to let the user know if they were successful. Even if the account was hard locked, I would return false but include in the message that the user should contact technical support if they believe they should have access with the password provided.
You don't mention what kind of data you're serving but if you're working in financial or health care data: make it so either the user can log in or they cannot, you shouldn't endeavor to give them any information as to why.
If you want you can tell them that the username is incorrect but you cannot suggest other usernames. And, of course, you cannot give any information about what might be wrong with the password, just tell them that it's incorrect.
About the code you presented, I realize you didn't really ask for coding advice, still, I do tons of code reviews and consistently see the same issues over-and-over with these roll-your-own authentication schemes. If your code is ever audited the auditor will likely find the following issues:
You must never hardcode passwords.
You must never persist a password in cleartext, always use an irreversible hash (SHA-1 or greater) when a password is received and only work with the hash value
Your applicatoon should 'fail-closed', meaning set up the accountList() function to return a 'not authorized' prior to the if statement and prior to calling any functions that would throw an exception (like a database access). Make the auth check in the if statemnt. That way if something fails in the things that the if statement calls (say an exception in data access or file i/o) the user fails to log in.

Django Rest Framework

I have a method that grabs POST data that is formated in json format like this
[{"UserName": "alexgv", "Password": "secretpassword"}]
Here is the method
def Login(request, *args):
data = request.DATA
return Response(data)
"""
try:
m = User.objects.get(UserName=request.DATA['UserName'])
if m.password == request.DATA['Password']:
request.session['member_id'] = m.id
return HttpResponse("Testing")
except User.DoesNotExist:
return HttpResponse("Your username and password didn't match.")
"""
I want to be able to take just one variable from that json POST. For example, maybe I just want to grab the UserName or Password. How would I do that? I have tried a variety of things but cant seem to get it to work, and I dont want to use request.POST.get because then that means I would have to send POST variables. BTW I am using this http://django-rest-framework.org/. I have read through the docs but cant seem to find anything in there. Any help is appreciated. What it returns right now is everything.
Like so...
username = request.DATA['UserName']
Incidentally, you probably shouldn't be writing session based API login views yourself as it's easy to do wrong.
For APIs that provide AJAX style functionality the you have two good options:
Login using a standard Django login, performed by the user, not performed by the API client.
Use a credentials based authentication scheme, rather than session based, and perform the login using AJAX. For example the Djoser third party package is a great library including token-based login and other similar views... https://github.com/sunscrapers/djoser
Update Also discovered https://github.com/JamesRitchie/django-rest-framework-sav which might be worth a look for AJAX session based authentication.

Categories

Resources