Implementing Flask login with hash password

A concise post that helps improve your authentication system

image

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.

Enjoyed this article?

Share it with your network to help others discover it

Continue Learning

Discover more articles on similar topics