2
votes

I am trying to use Flask-User (which incorporates Flask-Login). I would like to use the @login_required and @roles_required decorators to manage access to routes. However, it is not working.

The Flask-User documentation shows login_required being imported from flask_user, not flask_login. When imported from flask_login it works as expected, but if I switch it to be imported from flask_user it does not work. Instead, it says that an authenticated user is not signed in.

Why don't I just import from flask_login? Well, I could, but that does not solve my other problem which is that @roles_required can only be imported from flask_user, and that does not work either (same problem as above - says that authenticated users are not signed in).

My setup is below. I would be grateful if anyone could help me out, please. I have previously had Flask User working on another app, but can't figure out what's going wrong here. At the moment, the user database is stored in an SQLite database.

config.py

# Flask-User settings
    USER_APP_NAME = "App name"      # Shown in and email templates and page footers
    USER_ENABLE_EMAIL = True        # Enable email authentication
    USER_ENABLE_USERNAME = False    # Disable username authentication
    USER_EMAIL_SENDER_NAME = USER_APP_NAME
    USER_EMAIL_SENDER_EMAIL = "email"
    USER_ENABLE_AUTH0 = True

    USER_UNAUTHENTICATED_ENDPOINT = "login"
    USER_UNAUTHORIZED_ENDPOINT = "login"

__init__.py

# Initialise flask_login
login = LoginManager(app)
login.login_view = 'login'
login.refresh_view = "login"
login.needs_refresh_message = "Please sign in again to access this page"

models.py

from andon import db, app, login
from flask_user import UserMixin, UserManager
from flask_login import current_user

@login.user_loader
def load_user(id):
    return User.query.get(int(id))

class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)

    # User Authentication fields
    email = db.Column(db.String(255), nullable=False, unique=True)
    email_confirmed_at = db.Column(db.DateTime())
    password = db.Column(db.String(255), nullable=False)

    # User fields
    active = db.Column(db.Boolean())
    first_name = db.Column(db.String(50), nullable=False)
    last_name = db.Column(db.String(50), nullable=False)
    registered = db.Column(db.DateTime, default=datetime.utcnow)
    last_login = db.Column(db.DateTime)
    client_id = db.Column(db.Integer, db.ForeignKey('client.id', ondelete='CASCADE'))
    login_count = db.Column(db.Integer, default=0)
    invitation_token = db.Column(db.String(50))

    roles = db.relationship('Roles', secondary='user_roles')

    def check_password(self, password):
        # Check password against hash; return True or False
        return bcrypt.checkpw(password.encode('utf8'), self.password)

# Define the Role data model
class Roles(db.Model):
    id = db.Column(db.Integer(), primary_key=True)
    name = db.Column(db.String(50), nullable=False, unique=True)  # for @roles_accepted()

    def __repr__(self):
        return self.name


# Define the UserRoles association model
class UserRole(db.Model):
    __tablename__ = 'user_roles'
    id = db.Column(db.Integer(), primary_key=True)
    user_id = db.Column(db.Integer(), db.ForeignKey('user.id', ondelete='CASCADE'))
    role_id = db.Column(db.Integer(), db.ForeignKey('roles.id', ondelete='CASCADE'))

# Setup Flask-User
user_manager = UserManager(app, db, User)

main.py

from flask_login import current_user, login_user, logout_user
from flask_user import roles_required, login_required

@app.route('/test')
@login_required
def register_hub():

    return "Test"
2
It's refreshing to see a well-asked question! I'm not sure where to start debugging the issue, though; what version is flask-user in the current app and also the old app where you suggest that it worked?roganjosh
@roganjosh Thank you. I believe both are the most recent versionRob
Let's not go on beliefs. Import the libraries and use __version__ to be sureroganjosh
@roganjosh Both apps are using 1.0.2.2 of flask-user. They are using different versions of flask-login: 0.4.1 for the working app and 0.5.0 for the new one. I will try downgrading flask-login to 0.4.1 to see if that has any impactRob
@roganjosh Alas, same problem. If I'm signed out and try to access a page with @login_required (imported from flask-user) it correctly redirects to the login page. But if I'm signed in, it does the same. :(Rob

2 Answers

0
votes

After hours of tearing out my remaining hairs, I have found the issue. I had set USER_ENABLE_EMAIL = True, which I thought allowed flask-user to allow sign ins via email (as opposed to a username). What it actually does is require users to be email verified.

Since my user account did not have a flag saying it had been email verified, it was throwing as not having any login permissions. Annoying that flask-user does not throw a descriptive error in this circumstance, instead just saying that the user isn't signed in.

So, the fix: set USER_ENABLE_EMAIL = False, unless you want to force email verification before users have access to @login_required routes.

0
votes

I couldn't write a comment to your previous answer, so I will just quote the following and expand on it:

What it actually does is require users to be email verified.

The following setting, is as you thought, will allow users to login and register with an email address

 USER_ENABLE_EMAIL = True

what you want to do on top of that is to add the next setting to disable email confirmation (confirmation emails will not be send)

USER_ENABLE_CONFIRM_EMAIL = False

UserManager Settings docs