Question

I have multiple blueprints that needs to be integrated into a single app. I'm using flask-login to handle logins. However I'm confused on how to handle the LoginManager() and the .user_loader for my blueprints.

This is my current file structure.

system/
     run.py
     config.py
     app/
        __init__.py
        models.py
        views/
             blueprint1.py
             blueprint2.py
        static/
        templates/
              <templates>

What's the correct way to implement them? Do I just call them at the __init__.py and import the login manager variable at the blueprints? or Do I need to call them individually in the blueprints?

Hopefully I'm able to portray the question clearly. Thank you for reading and answering

Was it helpful?

Solution

You must understand that for one application you must use one login manager no matter how many blueprints you use (of course there can be specific exceptions for example when blueprints are independent, but in this case you probably can't use flask-login). Because:

  1. You have 1 entry point
  2. If user is not logged in, he will be redirected to login/registration page
  3. You have 1 user loader

How login manager works:

  1. It registers current_user in request context
  2. before_request reads your session, gets user id, loads the user with user_loader and set it to current_user or AnonymousUser
  3. When you visit the private page, login_required checks current_user.is_authenticated() else redirects to login page
  4. On login, it adds user id to the session

So you must initialize only one login manager instance for flask application and then use login_required and current_user in all your blueprints.

OTHER TIPS

This is how I have handled it:

This is where I am initialising everything:

import logging
import logging.config

import flask
import flask.globals as flask_global
import flask_login

from config import flask as flask_config
from rest.api import dashboard
from rest.api.util import login_decorator

logger = logging.getLogger(__name__)

# app
flask_app = flask.Flask(__name__)
flask_app.config.from_object(flask_config)

# login manager needs be set before blueprint registration
login_manager = flask_login.LoginManager()
login_manager.init_app(flask_app)

flask_app.register_blueprint(dashboard.blueprint)


# setting blueprint specific login view
# login_manager.login_view = "login"


@login_manager.user_loader
def load_user(user_id):
    """
    This will be used many times like on using current_user
    :param user_id: username
    :return: user or none
    """
    # http://librelist.com/browser/flask/2012/4/7/current-blueprint/#44814417e8289f5f5bb9683d416ee1ee
    blueprint = flask_global.current_app.blueprints[request.blueprint]

    if hasattr(blueprint, load_user):
        return blueprint.load_user(user_id)

    # https://flask-login.readthedocs.org/en/latest/#how-it-works
    return None

Here is my blueprint with its own handling of login:

from __future__ import absolute_import

import flask
import flask_login
from flask import Blueprint

from core.models.profile import Agent
from core.utils import thread_local
from rest.api.util import login_decorator

blueprint = Blueprint('human', __name__, url_prefix='/human')


def load_user(user_id):
    """
    This will be used many times like on using current_user
    :param user_id: username
    :return: user or none
    """
    agent = None
    try:
        agent = Agent.objects.get(username=user_id)
    except:
        # https://flask-login.readthedocs.org/en/latest/#how-it-works
        pass
    return agent


@blueprint.record_once
def on_load(state):
    """
    http://stackoverflow.com/a/20172064/742173

    :param state: state
    """
    blueprint.load_user = load_user
    state.app.login_manager.blueprint_login_views[blueprint.name] = 'human.login'


@blueprint.route('/login', methods=['POST'])
@login_decorator.login_not_required
def login():
    username = flask.request.args.get('username')
    password = flask.request.args.get('password')
    try:
        agent = Agent.objects.get(username=username)
    except:
        return 'Invalid username'

    if not agent.check_password(password):
        return 'Invalid password'

    flask_login.login_user(agent)

    return 'Valid login'


@blueprint.route("/logout")
def logout():
    flask_login.logout_user()
    return 'Logout done'


@blueprint.before_request
def before_request():
    agent = flask_login.current_user
    # https://flask-login.readthedocs.org/en/latest/#anonymous-users
    is_logged_in = agent.get_id() is not None

    login_not_required = getattr(flask.current_app.view_functions[flask.request.endpoint], 'login_not_required', False)
    is_static_resource_call = flask.request.endpoint.startswith('static/')

    if is_static_resource_call or is_logged_in or login_not_required:
        if is_logged_in:
            thread_local.set_current_brand_id(agent.brand_id)
    else:
        flask.abort(401)
        # if we want to redirect to some page then we can use this. The appropriate login_view should be set
        # return flask.current_app.login_manager.unauthorized()

Hope it helps.

In case anyone still faces this challenge due to the documentation not being so clear, here is a solution

In your case, you need to place the login manager declaration in the same file as the flask app instance. This is commonly an __init__.py file with the app = Flask(__name__). At the top, import LoginManager class

from flask_login import LoginManager

Then tie it to the app instance.

login_manager = LoginManager()
login_manager.init_app(app)

(This was not asked but just incase someone needs it) Lets say you have admins and normal users and you are authenticating from different tables:

@login_manager.user_loader
def load_user(user_id):
    x = Users.query.get(str(user_id))
    if x == None:
        x = Admins.query.get(str(user_id))
    return x

Finally after importing blueprints you can define the login views for each in a dictionary

login_manager.blueprint_login_views = {
    'admin': '/admin/login',
    'site': '/login',
}

Since you tied the login manager to the flask app instance, there is no need of importing it into any blueprint

The documentation is unclear and I will put down what I come up with after spending some time researching.

You only need to define login_manager once in the flask app.py and init_app. Then at each blueprint, add from flask_login import login_required at top and use the @login_required as usual. Turns out it can be used without stating the login_manager.

Code example (app with single blueprint)

app.py

import flask
from flask import redirect, url_for
import flask_login

from blueprint.secret import secret_bp
from model.user import User

login_manager = flask_login.LoginManager()

app = flask.Flask(__name__)
app.register_blueprint(secret_bp, url_prefix="/secret")

login_manager.init_app(app)

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

@app.route('/login', methods=['GET', 'POST'])
def login():
    if flask.request.method == "GET":
        return "<form action='/login' method='POST'><input type='text' name='user'><button type='submit'>Submit</button></form>"

    user = flask.request.form.get('user')
    if user == "user":
        # Login and validate the user.
        # user should be an instance of your `User` class
        flask_login.login_user(user)

        flask.flash('Logged in successfully.')

        return flask.redirect(next or flask.url_for('index'))
    return flask.redirect(flask.url_for('login'))

@app.route('/admin')
def admin():
    return "Admin page"

@login_manager.unauthorized_handler
def unauthorized():
    # do stuff
    return redirect(url_for('login'))

secret.py

from flask import Blueprint, render_template, abort
from jinja2 import TemplateNotFound
from flask_login import login_required

secret_bp = Blueprint('secret', __name__,
                        template_folder='templates')

@secret_bp.route('/noneed')
def no_need():
    return "You can see this without login."

@secret_bp.route('/needlogin')
@login_required
def show():
    return "can't see this if not login"

As expected, /secret/noneed can be access without login and /secret/needlogin will redirect you with the function stated with @unauthorized_handler.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top