Token based authentication with flask-security extension
Asked Answered
S

3

18

I am currently looking for a way to secure a REST API using token based authentication. I am developing the API in Python using Flask and have discovered the flask-security extension which seems to have a lot of interesting features.

One of the features mentioned in the documentation is Token Authentication.

According to the documentation:

Token based authentication is enabled by retrieving the user auth token by performing an HTTP POST with the authentication details as JSON data against the authentication endpoint. A successful call to this endpoint will return the user’s ID and their authentication token. This token can be used in subsequent requests to protected resources.

I am however still a bit confused on how to implement this feature using flask-security. Some online research has led me to using things such as @auth_token_required but I am having some trouble to put everything together. The flask-security documentation itself is not very helpful.

For example, how can a user get an authentication token? what is the authentication endpoints?

It would be great if you could lead me in the right direction. Code examples would be awesome too :-)

Snashall answered 8/12, 2014 at 11:22 Comment(1)
It worries me that such critical infrastructure is not well documented.Apgar
I
20

Endpoint is /login, you post your credentials as json request body:

{'email':'[email protected]', 'password':'1234'}

However for this to work you need to disable the csrf tokens in your flask app (thanks Mandar Vaze):

app.config['WTF_CSRF_ENABLED'] = False

Then you do each request with the token in the HTTP headers:

Authentication-Token:WyI1NTE1MjhmNDMxY2Q3NTEwOTQxY2ZhYTgiLCI2Yjc4NTA4MzBlYzM0Y2NhZTdjZjIxNzlmZjhiNTA5ZSJd.B_bF8g.t1oUMxHr_fQfRUAF4aLpn2zjja0

Or as query string:

http://localhost:5000/protected?auth_token=WyI1NTE1MjhmNDMxY2Q3NTEwOTQxY2ZhYTgiLCI2Yjc4NTA4MzBlYzM0Y2NhZTdjZjIxNzlmZjhiNTA5ZSJd.B_bF8g.t1oUMxHr_fQfRUAF4aLpn2zjja0

Client example in python 3:

import requests
import json

#do the login
r = requests.post('http://localhost:5000/login', 
                    data=json.dumps({'email':'[email protected]', 'password':'1234'}), 
                    headers={'content-type': 'application/json'})
response = r.json()
print(response) #check response
token = response['response']['user']['authentication_token']    #set token value

#Now you can do authorised calls
r = requests.get('http://localhost:5000/protected', 
                headers={'Authentication-Token': token})
print(r.text)

Angular example snippet to obtain the token:

$http.post('/login', {"email": $scope.formdata.login,"password":$scope.formdata.password}).
            success(function(results) {
            $window.sessionStorage.token = results.response.user.authentication_token;
            });

Angular example snippet to visit protected pages:

 if ($window.sessionStorage.getItem('token')) {
                config.headers['Authentication-Token'] = $window.sessionStorage.getItem('token');
            }
Ileenileitis answered 27/3, 2015 at 15:12 Comment(5)
Thanks. Do you use Flask-Security's token-based auth in production?Apgar
Currently only on development, I still don't have a definite answer to: do we need CSRF to enhance security in this case? See also: https://mcmap.net/q/670210/-flask-security-csrf-token/…Ileenileitis
Disabling CSRF is not acceptable unless you have no html views with forms.Mackenziemackerel
@Ileenileitis Can I use roles_accepted combined with token based authentication?Annabel
@Annabel sorry I don't know the answer to that questionIleenileitis
R
11

I found Flask-Security's token-based not a good candidate for my project. I recommend using JWT token instead.

The problems with Flask-Security's token based authentication.

  1. Need to disable CSRF globally, this is not good when you also have a traditional web application in which CSRF token is desirable
  2. No easy way to renew the token ( without submitting password again )
  3. Can not control the payload of the token, there's no API to put/get data to/from the token
  4. That token, by design, only works with one Flask app. So if your frontend app needs to talk with multiple restful apis, this wont work well

Check out JWT (pyjwt or flask-jwt) token, it solves all the above problems and more.

Relieve answered 21/7, 2016 at 0:15 Comment(5)
If you use wtforms you don't need to disable CSRF globally, you can use the @csrf_protect.exempt decorator on your API views.Wame
@bmjjr can you elaborate on not having to disable CSRF? How would one exempt the API login endpoint exclusively?Disgorge
@Disgorge with wtforms, for example in a Flask app, first you must initialize the CSRFProtect class and then call the init_app on it. I do this in my base.extensions.py file with a from flask_wtf import CSRFProtect and then initialize it like csrf_protect = CSRFProtect(). Then, in a base.app file in a create_app() function, I create the flask app object and then call csrf_protect.init_app(app) inside the create_app. Finally, you can import csrf_protect from your base.extensions file and use it as a decorator over with @csrf_protect.exempt over the API view function.Wame
I find that using app.config['WTF_CSRF_ENABLED'] = False also works. But, indeed, using JWTs is probably a better option, or at least a more standardized one.Yolande
You're comparing appple and bananas here. There's a ton of features in flask-security/login that you won't find in flask-jwt (password reset, federation, access list, etc.) If you need payload, just use jwt under the hood by overriding the two corresponding methods in User's model.Salcedo
S
2

Authentication endpoint is /login

Look at the code of flask-security here specifically views.py: _render_json()

login() calls _render_json which in turn calls get_auth_token() - and returns the auth token.

Problem (for me) is to get this to work. For me request.json seems empty (hence this does not work)

{"email": "[email protected]", "password": "test123"}

Hopefully this helps you move forward a little.

Sexpot answered 13/1, 2015 at 10:49 Comment(1)
In my case POST to /login works, but then obtained token does not work in Authentication-Token header. Anyway, you need to check what really is in the token. In old versions of Flask-Security it was just login-password encoded and decoded (if I understood correctly) - so much for security.Demoss

© 2022 - 2024 — McMap. All rights reserved.