Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Flask Login and Principal - current_user is Anonymous even though I'm logged in

I'm using Flask Login and Principal for identity and role management. My needs are described straight out of the docs. My code is here:

@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
    # Set the identity user object
    identity.user = current_user

    # Add the UserNeed to the identity
    if hasattr(current_user, 'get_id'):
        print 'current_user ' + str(current_user.get_id())
        identity.provides.add(UserNeed(current_user.get_id))

    # Assuming the User model has a list of roles, update the
    # identity with the roles that the user provides
    if hasattr(current_user, 'roles'):
        if current_user.roles:
            for role in current_user.roles:
                identity.provides.add(RoleNeed(role.name))

In my login code I do this:

identity_changed.send(current_app._get_current_object(),
                                  identity=Identity(user.user_id)

On login, the signal fires as expected. On each subsequent page load, the current_user is anonymous and doesn't have the user id yet all @login_required functions behave as if the user is logged in. Flask login knows that the user is logged in but for some reason the current_user is inconsistent.

Am I missing an essential point of configuration somewhere?

like image 310
fansonly Avatar asked Aug 12 '13 14:08

fansonly


2 Answers

I encountered the same problem! The root cause is that both Flask-Login and Flask-Principal are invoked by Flask during the "preprocess" stage of the request in the order that they were registered with your Flask app. If you register Flask-Principal before you register Flask-Login, then @identity_loaded.connect_via(app) will be called before @login_manager.user_loader, and therefore current_user will return the anonymous user.

The Flask-Principal documentation example shows a code excerpt where Flask-Principal is registered before Flask-Login. Tsk tsk! Here's what I ended up doing in my bootstrap:

login_manager = LoginManager()
login_manager.init_app(app)

# ...

principals = Principal(app) # This must be initialized after login_manager.

Then in my users.py view file:

@identity_loaded.connect_via(app)
def on_identity_loaded(sender, identity):
    """ This function is called by Flask-Principal after a user logs in. """

    identity.user = current_user

    if isinstance(current_user, User):
        identity.provides.add(UserNeed(current_user.id))

    for permission in user.permissions:
        # Do permission-y stuff here.

This solved the problem for me.

Edit: I submitted a bug report to the project for the documentation.

like image 184
Mark E. Haase Avatar answered Sep 22 '22 14:09

Mark E. Haase


Thank you for this, here is a related observation in case it is useful. I have been struggling with a similar problem of user roles not persisting in subsequent requests after login. Being a beginner, and playing with different things like flask-user (which I never got working) and settling on a) flask-principal and flask-login and b) flask-navigation instead of flask-nav.

This is so I can 1) easily control which menu items appear based on Principal and 2) avoid having the navigation markup generated outside of a template (as I was always taught to separate logic and presentation and writing a custom menu renderer for flask-nav just to change the surrounding HTML didn't seem right if I want to change the HTML later). I couldn't find a way to iterate through flask-nav objects or add custom properties to nav items whereas in flask-navigation I am creating a custom Item extending flask-navigation's Item to add permissions required.

The challenge I was also trying to solve was having a hierarchy of roles to prevent having to put complex Permission statements in my views (e.g. Admin is also editor, is also user, is also anonymous user etc.) Having googled furiously I couldn't find any concept of hierarchy like that. I also don't particularly want to have multiple roles assigned to a user in my model.

My mistakes were:

  • The above in the load order
  • As I hadn't yet put roles into my models so that there was a many to many relationship between User and Role, in my ignorance I did not realise that Flask-Login needed to load the roles in the @login_manager.user_loader function if the roles wheren't yet in the model. I instead assigned the roles in the login view after login_user(user) before issuing the flask-principal signal.
  • With my different approach the roles got assigned, but forgotten on the next request. This post gave me the clues I was searching for. This is what I ended up doing - all other Principal related code is as in the docs and above.
#CAVEATS - still learning Flask so this may not be the right approach and it is still a W.I.P.
#added a  kind of hierarchy order field to User to drive multiple roles in Permissions
#with this model I only have to assign one role to a user

class Role(db.Model):
    __tablename__ = 'roles'
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(50), unique=True)
    description = db.Column(db.String(200))
    hierarchy_order = db.Column(db.Integer)
    internal = db.Column(db.Boolean) # this is only so I can identify registered users and internal users
    users = db.relationship('User', backref='role',lazy='dynamic')

    def __repr__(self):
        return '<Role: {}>'.format(self.name)

# changed common flask-login example @login_manager.user_loader as follows

@login_manager.user_loader
def load_user(user_id):
    user = User.query.get(int(user_id))
    #work out what roles are below current role
    permissable_roles = Role.query.filter(Role.hierarchy_order<=user.role.hierarchy_order).all()
    user.roles = permissable_roles
    return user

I would dearly love to have this approach as a common convention, but I think I am stuck with having a loop in @login_manager.user_loader which assigns the multiple roles as a hierarchy working down from the assigned role. I hope some of this helps someone struggling with how it all ties together. I still have a lot to learn about where flask stores things and when they are available in different contexts.

like image 28
Simon F Avatar answered Sep 21 '22 14:09

Simon F