3
votes

I am using Flask Security for creating a secure API. I recently discovered that using @auth_token_required makes each one of my calls to the back end take approximately 50 times longer. Https call time is increased from 100ms per request to 5+ seconds. Replacing @auth_token_required with @login_required fixes the issue.

I found this thread on Github: Slow token authentication that describes the problem and frustration around this issue.

It seems that this is a part of intentional security throttling but it seems crazy to me to slow every request by a factor of 50. Do I need to move away from Flask Security and implement OAuth or does anyone in the community have an easy fix to this issue?

Please comment if you've encountered this issue and what you decided to do.

1

1 Answers

2
votes

I have played around with flask-security a bit trying to make it work for me in various scenarios. I have not needed to speed up hash so I may not have a straight answer so I may only point you in the right direction.

EDIT The official flask security in github is this. The links I used below point my fork which has several alterations so if you need to fork, fork the official one

1) Flask-Security uses passlib for hashing

2) As noted in the link you have shared, Flask-Security uses bcrypt which is intentionally slow

3) Bcrypt uses work factor to determine how long you would like it to take to hash data. In passlib, the work factor is represented by int variable called rounds

4 To hash data, flask security uses below function in flask-security/utils.py

def hash_data(data):
    return _hashing_context.hash(encode_string(data))

5 to verify your hashed data (eg a token) it uses this function in the same file

def verify_hash(hashed_data, compare_data):
    return _hashing_context.verify(encode_string(compare_data), hashed_data) 

6) The hashing_context is derived from passlib in flask-security/core.py like this in various places in the file

from passlib.context import CryptContext

def _get_hashing_context(app):
    schemes = cv('HASHING_SCHEMES', app=app)
    deprecated = cv('DEPRECATED_HASHING_SCHEMES', app=app)
    return CryptContext(
        schemes=schemes,
        deprecated=deprecated)

hashing_context=_get_hashing_context(app),

and finally in utils.py(earlier shared like this)

_hashing_context = LocalProxy(lambda: _security.hashing_context)

The CryptContext imported from passlib and used in six above can take another argument which is rounds a figure between 4 and 31, by default, it is 12 and as per the docs increasing the round by one DOUBLES the amount of time it takes

Since CryptContext has no round value passed to it in flask security, i assume it uses the default of 12 so you can experiment with various figures to see how it goes. Something like

def _get_hashing_context(app):
        schemes = cv('HASHING_SCHEMES', app=app)
        deprecated = cv('DEPRECATED_HASHING_SCHEMES', app=app)
        return CryptContext(
            schemes=schemes,
            deprecated=deprecated,
            pbkdf2_sha256__default_rounds=your_rounds) #I added this line

So you will need to fork security, change your rounds to a figure that works for you and install your repo like this

pip install git+https://github.com/your_repo/flask-security

Thats quite a mouthful of info, I hope it will be somehow useful. Let me know how it goes as I may need this in the not far future