flask-login: can't understand how it works

PythonMysqlFlask Login

Python Problem Overview


I'm trying to understand how Flask-Login works.

I see in their documentation that they use a pre-populated list of users. I want to play with a database-stored users list.

However, I don't understand some things in this Flask-Login module.

@login_manager.user_loader
def load_user(userid):
    #print 'this is executed',userid
    return user(userid, 'asdf')

This code will be called at every request? This is used to load all the details of my user object?

For now, I have this code:

@app.route('/make-login')
def make_login():
    username = 'asdf'
    password = 'asdf'
    user_data = authenticate(username, password)
    user_obj = user(user_data[0], user_data[1])
    login_user(user_obj)
    return render_template('make-login.html')

When I access /make-login, I want to log in.

My user class:

class user(object):
    def __init__(self, id, username, active=True):
        self.username = username
        self.id = id
        #self.active = active
    def is_authenticated(self):
        return True  
            
    def is_active(self):
        return True
    
    def is_anonymous(self):
        return False
    
    def get_id(self):
        return 5

Also, I wrote another two functions for authenticate/register

def authenticate(username, password):
    
    cursor = db.cursor()
    password = md5.md5(password).hexdigest()
    try:
        query = "SELECT * FROM `users` WHERE `username` = %s AND `password` = %s"
        cursor.execute(query, (username, password))
        results = cursor.fetchall()
        #print results[0][0]
        #print "here i am"
        if not results:
            return False
        else:
            user_data = [results[0][0], results[0][1]]
            return user_data
            #self.authenticated = True
            #self.user_id = results[0][0]
            #session['username']  = results['username']
            #print type(results)
    except db.Error, e:
        return 'There was a mysql error'    

def register(username, password, email, *args):
    cursor = db.cursor()
    password = md5.md5(password).hexdigest()
    try:
        #query = "INSERT INTO `users` (`username`, `password`, `email`) VALUES ('%s', '%s', '%s')" % (username, password, email)
        query = "INSERT INTO `users` (`username`, `password`, `email`) VALUES (%s, %s, %s)"
        cursor.execute(query, (username, password, email))
        db.commit()
        return True
    except db.Error, e:
        print 'An error has been passed. %s' %e
        db.rollback()
        return False

I don't know how to make this Flask-Login work with MySQL. Also, I don't know if the user are logged-in. How can I get the user ID or the username?

Anyone can explain me in some rows how this Flask-Login works?

Python Solutions


Solution 1 - Python

Flask-login doesn't actually have a user backend, it just handles the session machinery to help you login and logout users. You have to tell it (by decorating methods), what represents a user and it is also up to you to figure out how to know if a user is "active" or not (since being "active" can mean different things in different applications).

You should read the documentation and be sure what it does and does not do. Here I am only going to concentrate on wiring it up with the db backend.

To start off with, define a user object; which represents properties for your users. This object can then query databases, or LDAP, or whatever and it is the hook that connects the login mechanism with your database backend.

I will be using the login example script for this purpose.

class User(UserMixin):
    def __init__(self, name, id, active=True):
        self.name = name
        self.id = id
        self.active = active
    
    def is_active(self):
        # Here you should write whatever the code is
        # that checks the database if your user is active
        return self.active

    def is_anonymous(self):
        return False

    def is_authenticated(self):
        return True

Once you have the user object created, you need to write a method that loads the user (basically, creates an instance of the User class from above). This method is called with the user id.

@login_manager.user_loader
def load_user(id):
     # 1. Fetch against the database a user by `id` 
     # 2. Create a new object of `User` class and return it.
     u = DBUsers.query.get(id)
    return User(u.name,u.id,u.active)

Once you have these steps, your login method does this:

  1. Checks to see if the username and password match (against your database) - you need to write this code yourself.

  2. If authentication was successful you should pass an instance of the user to login_user()

Solution 2 - Python

Flask-login will try and load a user BEFORE every request. So yes, your example code below will be called before every request. It is used to check what userid is in the current session and will load the user object for that id.

@login_manager.user_loader
def load_user(userid):
    #print 'this is executed',userid
    return user(userid, 'asdf')        

If you look at the Flask-login source code on github, there is a line under function init_app which goes:

app.before_request(self._load_user)

So before every request, the _load_user function is called. The _load_user functions actually calls another function "reload_user()" based on conditions. And finally, reload_user() function calls your callback function that you wrote (load_user() in your example).

Also, flask-login only provides the mechanism to login/logout a user. It does not care if you are using mysql database.

Solution 3 - Python

As per from the Flask-Login's document a user object must be returned and if the user id is not found it should return None instead of Exception.

@login_manager.user_loader
def load_user(userid):
    try:
        #: Flask Peewee used here to return the user object
        return User.get(User.id==userid)
    except User.DoesNotExist:
        return None

Solution 4 - Python

You might want to use Flask-Security, which combines Flask-Login with SQLAlchemy for database access and automates much of the back-end handling of user records.

The Quick Start tutorial will get you started. Set app.config['SQLALCHEMY_DATABASE_URI'] to your MySQL database connection string.

Solution 5 - Python

After great everything explained I will try with the code to give a simple example of how to use and at the same time answer it below:

> Once you have these steps, your login method does this: > > 1. Checks to see if the username and password match (against your database) - you > need to write this code yourself. > 2. If authentication was successful you should pass an instance of the user to > login_user()

Let's say this is the structure of the project:

├─stackoverflow
  │   run.py
  │
  └───site
      │   forms.py
      │   models.py
      │   routes.py
      │   site.db
      │   __init__.py
      │
      ├───static
      │       main.css
      │
      └───templates
              about.html
              account.html
              home.html
              layout.html
              login.html
              register.html

What interests us most is the model:

# models.py
from site import db, login_manager
from flask_login import UserMixin


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


class User(db.Model, UserMixin):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    email = db.Column(db.String(120), unique=True, nullable=False)
    image_file = db.Column(db.String(20), nullable=False, default='default.jpg')
    password = db.Column(db.String(60), nullable=False)

    def __repr__(self):
        return f"User('{self.username}', '{self.email}', '{self.image_file}')"

And we will call it in the user's login, more precisely after the user has registered - after the user exists.

Specifically, the answer to the two steps that need to be implemented can be found in the following two lines of code:

> 1. Checks to see if the username and password match (against your database) - you > need to write this code yourself.

A: if user and bcrypt.check_password_hash(user.password, form.password.data):

> 2. If authentication was successful you should pass an instance of the user to > login_user()

A: login_user(user, remember=form.remember.data)

# routes.py

from flask import render_template, url_for, flash, redirect, request
from site import app, db, bcrypt
from site.forms import RegistrationForm, LoginForm
from site.models import User
from flask_login import login_user, current_user, logout_user, login_required



@app.route("/")
@app.route("/home")
def home():
    return render_template('home.html', title="Welcome")


@app.route("/register", methods=['GET', 'POST'])
def register():
    if current_user.is_authenticated:
        return redirect(url_for('home'))
    form = RegistrationForm()
    if form.validate_on_submit():
        hashed_password = bcrypt.generate_password_hash(form.password.data).decode('utf-8')
        user = User(username=form.username.data, email=form.email.data, password=hashed_password)
        db.session.add(user)
        db.session.commit()
        flash('Your account has been created! You are now able to log in', 'success')
        return redirect(url_for('login'))
    return render_template('register.html', title='Register', form=form)


@app.route("/login", methods=['GET', 'POST'])
def login():
    if current_user.is_authenticated:
        return redirect(url_for('home'))
    form = LoginForm()
    if form.validate_on_submit():
        user = User.query.filter_by(email=form.email.data).first()
        if user and bcrypt.check_password_hash(user.password, form.password.data):
            login_user(user, remember=form.remember.data)
            next_page = request.args.get('next')
            return redirect(next_page) if next_page else redirect(url_for('home'))
        else:
            flash('Login Unsuccessful. Please check email and password', 'danger')
    return render_template('login.html', title='Login', form=form)


# Views that require your users to be logged in can be decorated with the `login_required` decorator

@app.route("/account")
@login_required
def account():
    return render_template('account.html', title='Account')


# When the user is ready to log out:

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

You can then access the logged-in user with the current_user proxy, which is available in every template:

{% if current_user.is_authenticated %}
  Hi {{ current_user.username }}!
{% endif %}

By default, when a user attempts to access a login_required view without being logged in, Flask-Login will flash a message and redirect them to the log in view. (If the login view is not set, it will abort with a 401 error.)

The name of the log in view can be set as LoginManager.login_view:

login_manager.login_view = 'login'

# __init__.py

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_bcrypt import Bcrypt
from flask_login import LoginManager

app = Flask(__name__)
app.config['SECRET_KEY'] = 'ENTER_SECRET_KEY'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///site.db'
db = SQLAlchemy(app)
bcrypt = Bcrypt(app)
login_manager = LoginManager(app)
login_manager.login_view = 'login'
login_manager.login_message_category = 'info'

from site import routes

And finally, run the project:

# run.py

from site import app

if __name__ == '__main__':
    app.run(debug=True)
I hope that, in addition to a great explanation, this simple example is helpful.

Solution 6 - Python

Here is a Flask example of using login: https://bitbucket.org/leafstorm/flask-login/src/3160dbfc7cfc/example/login-example.py You need to use @login_required for every method that requires login. For example,

@app.route('/make-login')
@login_required
def make_login():
    ...

Solution 7 - Python

flask-login asks for a User object per request by calling user_loader.

If you use DB each time, you can expect a performance hit. (accepted answer suffers from this)

Your login route, on the other hand, is only called once during the session.

So the typical (session based) implementation should:

  • Fetch data from DB in your /login route, and cache it in session
  • Load user from cache in user_loader

Something like this:

@app.route("/login")
def login_callback():
    user_data=my_fetch_from_db_based_on_whatever()
    if my_check_credentials_ok(user_data)
        session["user"]=user_data
        login_user(User(user_data))
    else:
        abort(400)
 :

@login_manager.user_loader
def load_user(user_id):
    user_data = session["user"]
    user=User(user_data)
    return user if user.userid==user_id else None

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionSorin VladuView Question on Stackoverflow
Solution 1 - PythonBurhan KhalidView Answer on Stackoverflow
Solution 2 - PythoncodegeekView Answer on Stackoverflow
Solution 3 - PythonbeebekView Answer on Stackoverflow
Solution 4 - PythonSteve SaportaView Answer on Stackoverflow
Solution 5 - PythonMilovan TomaševićView Answer on Stackoverflow
Solution 6 - PythonDrSkippyView Answer on Stackoverflow
Solution 7 - PythonOmri SpectorView Answer on Stackoverflow