How to implement user_loader callback in Flask-Login How to implement user_loader callback in Flask-Login flask flask

How to implement user_loader callback in Flask-Login


You will need to load the user object from the DB upon every request. The strongest reason for that requirement is that Flask-Login will check the authentication token every time to ensure its continuing validity. The calculation of this token may require parameters stored on the user object.

For example, suppose a user has two concurrent sessions. In one of them, the user changes their password. In subsequent requests, the user must be logged out of the second session and forced to login anew for your application to be secure. Think of the case where the second session is stolen because your user forgot to log out of a computer - you want a password change to immediately fix the situation. You might also want to give your admins the ability to kick a user out.

For such forced logout to happen, the authentication token stored in a cookie must 1) be based in part on the password or something else that changes each time a new password is set; 2) be checked before running any view, against the latest known attributes of the user object - which are stored in the DB.


I do share your concerns Edmond: hitting database each time when one needs to know user's role or name is insane. Best way would be to store your User object in session or even application-wide cache which gets updated from the DB each couple of minutes. I personally use Redis for that (that way website can be run by multiple threads/processes while using single cache entry point). I just make sure Redis is configured with password and non-default port, and any confidential data (like user hashes etc) are stored there in an encrypted form. Cache can be populated by a separate script running on specified interval, or separate thread can be spawned in Flask. Note: Flask-Session can be also configured to use (the same) redis instance to store session data, in that case instance with 'bytes' datatype will be needed, for a regular cache you might often go with instance type which automatically translates bytes into strings (decode_responses=True).


Here is my code, another User as data mapping object provide query_pwd_md5 method.

User login:

@app.route('/users/login', methods=['POST'])def login():    # check post.    uname = request.form.get('user_name')    request_pwd = request.form.get('password_md5')    user = User()    user.id = uname    try:        user.check_pwd(request_pwd, BacktestUser.query_pwd_md5(            uname, DBSessionMaker.get_session()        ))        if user.is_authenticated:            login_user(user)            LOGGER.info('User login, username: {}'.format(user.id))            return utils.serialize({'userName': uname}, msg='login success.')        LOGGER.info('User login failed, username: {}'.format(user.id))        return abort(401)    except (MultipleResultsFound, TypeError):        return abort(401)

User class:

class User(UserMixin):"""Flask-login user class."""def __init__(self):    self.id = None    self._is_authenticated = False    self._is_active = True    self._is_anoymous = False@propertydef is_authenticated(self):    return self._is_authenticated@is_authenticated.setterdef is_authenticated(self, val):    self._is_authenticated = val@propertydef is_active(self):    return self._is_active@is_active.setterdef is_active(self, val):    self._is_active = val@propertydef is_anoymous(self):    return self._is_anoymous@is_anoymous.setterdef is_anoymous(self, val):    self._is_anoymous = valdef check_pwd(self, request_pwd, pwd):    """Check user request pwd and update authenticate status.    Args:        request_pwd: (str)        pwd: (unicode)    """    if request_pwd:        self.is_authenticated = request_pwd == str(pwd)    else:        self.is_authenticated = False