8
votes

I am trying to build a super simple web app for my own use. I'll be the only user, so I don't feel the need to involve a database for user management. I'm trying to use flask-login, but even though my call to login_user succeeds, I'm still met with a 401-Unauthorized page after the redirect to a page with @login_required. Here is the entirety of my app:

from flask import Flask, render_template, request, flash, redirect, url_for
from flask.ext.login import LoginManager, login_user, logout_user, current_user, login_required, UserMixin

app = Flask(__name__, static_url_path="")
app.secret_key = "[redacted]"

login_manager = LoginManager()
login_manager.init_app(app)
#login_manager.login_view = "login"

class User(UserMixin):
    def __init__(self, id):
        self.id = id

nathan = User('nathan')

@login_manager.user_loader
def load_user(userid):
    if userid == 'nathan':
        return nathan
    else:
        return None

@app.route("/logout")
@login_required
def logout():
    logout_user()
    return redirect(url_for('login'))

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

@app.route("/login", methods=["GET", "POST"])
def login():
    if request.method == 'POST':
        if request.form['username'] == 'nathan'\
                and request.form['password'] == '[redacted]':
            login_user(nathan, remember=True)
            flash('logged in...', 'success')
            return redirect(request.args.get("next") or url_for("index"))
        else:
            flash('Incorrect username or password. Try again.', 'error')

    return render_template("login.html");

if __name__ == "__main__":
    app.run(host="0.0.0.0", debug=True)

I've verified that the login actually succeeds (login_user returns true) and that load_user is returning the nathan object.

[EDIT] Also, I'm currently using the Flask development server, in case that's relevant.

Not sure where I'm going wrong. Thanks in advance!

1
I'm not sure you can use flask-login without a database. It needs some way to store your session.computmaxer
I thought session info was all stored in cookies, not a database.gnathan
I would suggest reading up on cookies a bit more. In short, cookies are things the browser stores on a per-domain basis that are sent along with every request to that domain. Session cookies simply consist of a session ID, which the application is responsible for looking up upon receiving the request. The application still needs to store some information about the session (such as expire time) which corresponds to the session ID. Flask-login handles all of this for you, but it needs some way to store those sessions.computmaxer
I don't see any requirements for a database interface in the flask-login documentation. In fact, it says that it does not "impose a particular database or other storage method on you. You are entirely in charge of how the user is loaded." How would it even interface with the database if I had one? I don't, for example, see that the LoginManager or anything else requests database credentials, nor does the User class interface specify any set* methods or similar which I could use to store something in a database.gnathan
just wasted too much time on this one :-/ SharedDataMiddleWare is the way to go.craigb

1 Answers

4
votes

Update:

Since a newer version(0.2.2) of Flask-Login this is no more an issue. Check out the changes in this commit.

If you are using an older version, read on.


The problem here is static_url_path="". For Flask-Login to work you can not have an empty string static_url_path.

The following lines in the Flask-Login source(older version) reveal this:

if (current_app.static_url_path is not None and
    request.path.startswith(current_app.static_url_path)
):
    # load up an anonymous user for static pages
    _request_ctx_stack.top.user = self.anonymous_user()
    return

Since your static_url_path is "" the if condition evaluates to True, because of which every page you visit acts like a static page, and hence Flask-Login always loads an anonymous user, instead of continuing to load the actual user(using the load_user callback).


Also do not forget to uncomment #login_manager.login_view = "login"


If you still want to use the root folder of the app itself as the static folder, take a look at this solution, using SharedDataMiddleWare:

app.debug = True
if app.config['DEBUG']:
    from werkzeug import SharedDataMiddleware
    import os
    app.wsgi_app = SharedDataMiddleware(app.wsgi_app, {
      '/': os.path.dirname(__file__)
    })

if __name__ == "__main__":
    app.run(host="0.0.0.0")