circuit

Implementing Flask login with hash password

A concise post that helps improve your authentication system


Flask is a minimalist Python framework, that possibilities high customization in your Design Patterns.

Assuming that our project already has your database connection already configured we will start with the following configuration:

from flask import Flask
from flask_sqlalchemy import SQLAlchemy
from flask_script import Manager
from flask_migrate import Migrate, MigrateCommand
from flask_login import LoginManager

app = Flask(__name__)
app.config.from_object('config')

login_manager = LoginManager(app)

db = SQLAlchemy(app)
migrate = Migrate(app, db)

manager = Manager(app)
manager.add_command('db', MigrateCommand)

from app.models import tables, forms
from app.controllers import default

Our forms:

from flask_wtf import FlaskForm
from wtforms import StringField, PasswordField, BooleanField
from wtforms.validators import DataRequired, Email


class LoginForm(FlaskForm):
    username = StringField("username", validators=[DataRequired()])
    password = PasswordField("password", validators=[DataRequired()])
    remember_me = BooleanField()


class RegisterForm(FlaskForm):
    username = StringField("username", validators=[DataRequired()])
    password = PasswordField("password", validators=[DataRequired()])
    name = StringField("name")
    email = StringField("email", validators=[DataRequired(), Email()])

our tables:

from app import db, login_manager
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

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

class User(db.Model, UserMixin):
    __tablename__ = 'users'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(80), unique=True, nullable=False)
    password = db.Column(db.String(80))
    name = db.Column(db.String(80))
    email = db.Column(db.String(120), unique=True, nullable=False)

    def __init__(self, username, password, name, email):
        self.username = username
        self.password = generate_password_hash(password)
        self.name = name
        self.email = email

    def __repr__(self):
        return f'<User {self.username}>'

    def verify_password(self, pwd):
        return check_password_hash(self.password, pwd)

In the example above we are making the User model extends UserMixin so that we don’t need implement the functions is_authenticated, is_active, is_anonymous and get_id, necessary to implement of login.

In this case I use the native werkzeug lib of python to generate the hash in the User model constructor and the password verification with the check_password_hash method that returns true if password ok.

In controllers we have the functions (index, register, login e logout):

Register controller:

...

@app.route('/register', methods=['GET', 'POST'])
def register():
    form = RegisterForm()
    username = form.username.data
    password = form.password.data
    name = form.name.data
    email = form.email.data

    if form.validate_on_submit():
        user = User.query.filter_by(username=username).first()
        if not user:
            user = User(username, password, name, email)
            db.session.add(user)
            db.session.commit()

        return redirect(url_for('login'))

    else:
        return render_template('register.html', form=form)

In Register controller, we use the data provided by the user to create a new user in the system.

Login controller:

...

@app.route("/login", methods=["GET", "POST"])
def login():
    form = LoginForm()
    username = form.username.data
    password = form.password.data

    if form.validate_on_submit():
        user = User.query.filter_by(username=username).first()

        if user and user.verify_password(password):
            login_user(user)
            flash("Usuário Logado!")
        else:
            flash("Login ivalido!")
    else:
        print(form.errors)

    return render_template('login.html', form=form)

In Login controller, we use the username and password provided by the user to authenticate him with the flask_login method login_user and we send a message if the user is authenticated or not.

Index controller:

...

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

In Index controller, the decorator @login_required was used so that only logged-in users can access the main page

Logout controller:

...

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

In Logout controller, the current user logged-in in session is logged out with the flask_login method logout_user and a redirection is made to the login page.

You can see the full project in my github.




Continue Learning