59: Improving Login Registration with Flask‐Login and Bcrypt - MantsSk/CA_PTUA14 GitHub Wiki

Improving Login/Registration with Flask-Login and Bcrypt

To follow this lesson, please use the material that we finished with in the previous lesson: https://github.com/MantsSk/CA_PTUA10/tree/master/58Pamoka%20-%20FlaskLoginSessions/end_code

flask-login

In the previous lesson, we managed user sessions using Flask's built-in session management. While this approach works, it requires more manual effort and leaves room for potential security vulnerabilities. Flask-Login offers a more robust solution for managing user sessions and authentication.

Why Flask-Login is Better Than Managing Sessions Yourself:

  • Security: Flask-Login handles session management securely, reducing the risk of common session-related vulnerabilities.
  • Simplicity: Flask-Login abstracts away many of the complexities of session management and user authentication, making it easier to implement and maintain these features in your application.
  • Robustness: Flask-Login is a widely used and well-maintained extension for Flask, ensuring that it stays up-to-date with the latest security best practices and features.

Installation

We can install the package, by using: pip install flask_login

Modifying existing code to add flask-login capabilities

Modify the User class and add new methods

from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user

login_manager = LoginManager(app)
login_manager.login_view = 'home'

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

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

Let's go through this code:

  • We import necessary modules from Flask-Login, including LoginManager, UserMixin, and utility functions like login_user, login_required, logout_user, and current_user: from flask_login import LoginManager, UserMixin, login_user, login_required, logout_user, current_user

  • We create an instance of LoginManager and associate it with our Flask application (app). This enables Flask-Login to work seamlessly with our application and we also set the view function that Flask-Login will redirect to when a user tries to access a protected route without being logged in. In our case, our default login_view will be 'home' route:

login_manager = LoginManager(app)
login_manager.login_view = 'home'
  • We define a User class representing users in our application. This class inherits from UserMixin and db.Model. UserMixin is a class provided by Flask-Login that helps in implementing the methods required for user authentication and session management such as such as is_authenticated, is_active, is_anonymous, and get_id:
class User(UserMixin, db.Model):
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(20), unique=True, nullable=False)
    password = db.Column(db.String(60), nullable=False)
  • We register a user loader function using the @login_manager.user_loader decorator. This function loads users from the database based on their ID. Inside this function, we use SQLAlchemy's query interface to retrieve the user with the given ID from the database.
@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

Modify login route

Our login route will be modified very minimally:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()
        if user and user.password == password:
            login_user(user)
            return redirect(url_for('home'))
        else:
            return render_template('login.html', message='Invalid username or password')
    return render_template('login.html')

You can see the new line here: login_user(user)

login_user(user) is a utility function provided by Flask-Login. Its purpose is to log in a user by setting the appropriate session variables and flags. After verifying the user's credentials (e.g., checking if the username and password match), we call login_user and pass the user object as an argument.

Modifying the logout route

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

Modifying the logout route is quite similar, we just use built-in flask login capabilities and the logout_user method provided to us. It also has a @login_required decorator, which will only let this method be executed when user is logged in. Avoiding any security or other issues.

Making registered username visible after login:

Let's modify our home route, we will use current_user object that flask-login provides in our template, so no need to pass our username session variable anymore. Our "home" route can look like this:

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

We will modify base template to look like this:

<div class="navbar">
    <a class="{% if active == 'home' %}active{% endif %}" href="{{ url_for('home') }}">Home</a>
    {% if current_user.is_authenticated %}
        <a href="{{ url_for('new_post') }}">Add post</a>
        <a href="{{ url_for('logout') }}">Logout</a>
        <span style="float:right; color:black; padding-right: 20px;">Hello, {{ current_user.username }}!</span>
    {% else %}
        <a href="{{ url_for('register') }}">Register</a>
        <a href="{{ url_for('login') }}">Login</a>
    {% endif %}
</div>

We replaced username session variable with current_user object that is accessible in template.

We check if user is authenticated with current_user.is_authenticated():

    {% if current_user.is_authenticated %}

And we print out the username by using:

        <span style="float:right; color:black; padding-right: 20px;">Hello, {{ current_user.username }}!</span>

Registration and bcrypt

Currently, we store the passwords in plain text in our database, which is a very bad security practice. Let's fix that.

flask-bcrypt installation

pip install flask-bcrypt

Modify existing register route

from flask_bcrypt import Bcrypt

bcrypt = Bcrypt(app)

@app.route('/register', methods=['GET', 'POST'])
def register():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()
        if user:
            return render_template('register.html', message='Username already exists')
        hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')
        new_user = User(username=username, password=hashed_password)
        db.session.add(new_user)
        db.session.commit()
        return redirect(url_for('login'))
    return render_template('register.html')

We modified a few lines in our register route:

hashed_password = bcrypt.generate_password_hash(password).decode('utf-8')

We still use the same password that user provides, but this time we hash a password with bcrypt.generatete_password_hash_method() and we add it to the database.

If you would take a look at the database, you would see the result: image

Fixing login

However, there is one problem. We cannot log in anymore! That is because our login method doesn't check for hashing! We can easily do this by modifying our login code:

@app.route('/login', methods=['GET', 'POST'])
def login():
    if request.method == 'POST':
        username = request.form['username']
        password = request.form['password']
        user = User.query.filter_by(username=username).first()
        if user and bcrypt.check_password_hash(user.password, password):
            login_user(user)
            return redirect(url_for('home'))
        else:
            return render_template('login.html', message='Invalid username or password')
    return render_template('login.html')

We use bcrypt.check_password_hash to compare value from database to our applied hashing and see if the values are the same.

Summary

In this lesson, we significantly improved the security and functionality of our Flask application by integrating Flask-Login for user authentication and Flask-Bcrypt for secure password hashing. With these enhancements, our Flask application now offers secure user authentication and password management

Exercise!

1:

Fix this Flask application and the problems it has:

Use provided credentials to log in: username - code password - academy

  1. If launching the application, the user is greeted with an error message.
  2. If logged in, the username is not shown in the navbar, instead we get this message - "Hello, WHAT?!!".
  3. After logging in, the user is redirected to register again!
  4. Logout is not working.
  5. Newly registered users are not able to log in. Why? Can you fix that?

2:

Modify this exercise code and convert all Flask HTML forms to Flask WTF forms.

⚠️ **GitHub.com Fallback** ⚠️