0
votes

When using Flask Login (for the first time) and following every tutorial example I came across, after correctly POSTing credentials, calling login_user(), and redirecting to a protected login_required route, I'm still getting a 401 Unauthorised response. Only when I explicitly set the is_authorised property to True does it work. But this seems like it should be unnecessary, and no tutorial example I can find does this. What am I overlooking? Sample code below. Thanks!

from flask import Flask, render_template, url_for, jsonify, session, redirect, request
from flask_login import LoginManager, login_required, login_user, current_user

login_manager = LoginManager()
app = Flask(__name__)
app.secret_key = b'_5#y2L"F4Q8z\n\xecgwefweligli]/'
login_manager.init_app(app)

class User:
    _users = {
        'me': 'mypass'
    }

    _active_users = {}

    def __init__(self, user_id):
        self.user_id = user_id
        self.password = self._users[user_id]
        self._authenticated = False
        self._active = True

    @property
    def is_active(self):
        return self._active

    @property
    def is_authenticated(self):
        return self._authenticated

    @is_authenticated.setter
    def is_authenticated(self, value):
        if value:
            self._authenticated = True
        else:
            self._authenticated = False

    @property
    def is_anonymous(self):
        return False

    def get(user_id):
        if user_id in User._active_users:
            return User._active_users[user_id]
        if user_id in User._users:
            User._active_users[user_id] = User(user_id)
            return User._active_users[user_id]
        return None

    def get_id(self):
        return self.user_id

@login_manager.user_loader
def load_user(user_id):
    return User.get(user_id)

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        u = User.get(request.form['username'])
        app.logger.debug(u)
        if u and u.password == request.form['password']:
            app.logger.debug('authenticated')
            r = login_user(u)
            u.is_authenticated = True  # HERE: this shouldn't be necessary?
            app.logger.debug(u.is_authenticated)
            app.logger.debug(current_user.is_authenticated)
            return redirect(url_for('index'))
        return redirect(url_for('login'))
    return render_template('login.html')

@app.route('/')
@login_required
def index():
    return render_template('main.html')

When I remove:

u.is_authenticated = True

the user is not authenticated. I've looked at the source for login_user() and it doesn't seem to set is_authenticated. Am I doing it right? I'm just confused because no documentation / tutorial includes the above line, but all claim to just work.

1
Why are you not using the UserMixin from flask_login and using that in your User class? I'm pretty sure all tutorials will use thatroganjosh
Thanks roganjosh - Just tried that and it does work. Still none the wiser (having looked at source for UserMixin) as to why my original code needed to explicitly set is_authenticated. Appreciate the help.user1974767

1 Answers

1
votes

Your original code needed to explicitly set is_authenticated because from one request to the next, a new instance of the User object is created each time, so the information that the user already authenticated is lost.

To avoid the loss of this information we need a place to store it. While Flask-login helpfully provides a UserMixin class that provides default implementations of this, you can just add authenticated field in the User model:

authenticated = db.Column(db.Boolean, default=False)

When the user login, you should change authenticated to true and save change in database and When the user logout, you should change authenticated to false and save change in database.

The is_authenticated function will be similar to this:

@property
def is_authenticated(self):
    return self.authenticated

Please find this tutorial.