17
votes

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 :-)

3
It worries me that such critical infrastructure is not well documented.Dr. Jan-Philip Gehrcke

3 Answers

20
votes

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

{'email':'john@smit.com', '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':'john@smit.com', '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');
            }
11
votes

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.

2
votes

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": "test@example.com", "password": "test123"}

Hopefully this helps you move forward a little.