Skip to content
This repository was archived by the owner on Jul 30, 2024. It is now read-only.
This repository was archived by the owner on Jul 30, 2024. It is now read-only.

Slow token authentication #731

Description

@KPilnacek

Hello guys,

at my current job, I happened to be a part of a backend team, which is creating an API.
The API should be then served to javascript application and needs to be quite fast (100 ms or so).
However, it is not.
After some profiling, we figured out that it is the token authentication in Flask-security, which is holding us back (pleas see the MWE).

MWE

import flask
from flask_security import Security, SQLAlchemyUserDatastore, UserMixin, RoleMixin, auth_required
from flask_sqlalchemy import SQLAlchemy

app = flask.Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////tmp/database.sqlite3'
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['WTF_CSRF_ENABLED'] = False
app.config['SECURITY_TOKEN_AUTHENTICATION_HEADER'] = 'Authorization'
app.config['SECURITY_PASSWORD_HASH'] = 'pbkdf2_sha512'
app.config['SECURITY_PASSWORD_SALT'] = b'secret'
app.config['SECRET_KEY'] = "super_secret"

db = SQLAlchemy(app)

roles_users = db.Table('roles_users',
                       db.Column('user_id', db.Integer(), db.ForeignKey('user.id')),
                       db.Column('role_id', db.Integer(), db.ForeignKey('role.id')))


class Role(db.Model, RoleMixin):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(80), unique=True)
    description = db.Column(db.String(255))


class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    email = db.Column(db.String(255), unique=True)
    password = db.Column(db.String(255))
    active = db.Column(db.Boolean())
    confirmed_at = db.Column(db.DateTime())
    roles = db.relationship('Role', secondary=roles_users,
                            backref=db.backref('users', lazy='dynamic'))


user_datastore = SQLAlchemyUserDatastore(db, User, Role)

# Setup Flask-Security
security = Security(app, user_datastore)

db.drop_all()
db.create_all()

admin_role = Role(**{'name': 'admin', 'description': 'Admin role'})
db.session.add(admin_role)
db.session.commit()

user_datastore.create_user(email='test@example.com', password='test', active=True, roles=[Role.query.first()])
db.session.commit()


@app.route('/')
@auth_required('basic', 'token')
def hello():
    return flask.jsonify({'hello': 'world'})


if __name__ == '__main__':
    app.run(debug=True)

Basic authentication timing

The timings are perfect (bellow 100 ms) but that is not the way we should do it.

time curl http://127.0.0.1:5000/ -u "test@example.com:test"
{
  "hello": "world"
}


real    0m0.076s
user   0m0.008s
sys     0m0.006s

Token authentication timing

Getting the token is OK.

curl -H "Content-Type: application/json" -X POST -d '{"email":"test@example.com","password":"test"}' http://127.0.0.1:5000/login

{
  "meta": {
    "code": 200
  }, 
  "response": {
    "user": {
      "authentication_token": "WyIxIiwiJDUkcm91bmRzPTUzNTAwMCRFRUpLRFNONlB2L1hzL2lRJDhMWFZvZlpLMmVoa1BVdWtpRlhUR1lvNEJ3T3FjS3dKMVhVWGlOczRwZDMiXQ.DOLjcQ.oBrT4gr1m49rISyxhaj9Lxu1VNk", 
      "id": "1"
    }
  }
}

But than the request is terribly slow.
The timings are 20 times slower 😳 😮.

time curl "http://127.0.0.1:5000/?auth_token=WyIxIiwiJDUkcm91bmRzPTUzNTAwMCRFRUpLRFNONlB2L1hzL2lRJDhMWFZvZlpLMmVoa1BVdWtpRlhUR1lvNEJ3T3FjS3dKMVhVWGlOczRwZDMiXQ.DOLjcQ.oBrT4gr1m49rISyxhaj9Lxu1VNk"
{
  "hello": "world"
}

real    0m2.371s
user   0m0.005s
sys     0m0.006s

What with that???

I know that Flask-security wraps together several other flask security packages (Flask-login, Flask-WTF, ...).

  1. Do you know what could be the cause? (is it Flask-security or Flask-login or something deeper?)
  2. It seems that the hashing algorithm, which is slow, is running for every request.
    However, it might not be necessary to do it every time.
    It should be enough, to only store the token and check if the incoming token is the same as the stored one.
    Is there a way to do it like that (either with Flask-security or not)?
  3. Can I set the app (app.config) a different way to make it faster (still using the token auth.)?
  4. Is there a workaround (still using Flask-security)?
  5. Shall I write it myself? Is it the Flask-security holding us back?
  6. Any other ideas?

I have cross-posted this issue on StackOverflow.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions