Authenticating Users with Login Functionality

Authenticating Users with Login Functionality

Welcome to the lesson on implementing user login functionality in your Flask ToDo App. In previous lessons, we've set up the authentication middleware and added secure user registration. Building on these foundations, we'll now focus on allowing registered users to log in. This step is vital as it ensures a secure and personalized experience for users interacting with the app. You will learn how to authenticate users using their credentials and maintain their session for secure access to the app's features.

Checking Hashed Password

You may recall from our user registration lesson that we set up a User model to store user data securely. Now, let's create the check_password method, which will be crucial for verifying user credentials during login. This method will compare the hash of a provided password with the stored password hash in the database to ensure validity.

Here's how you can implement it in app/models/user.py:

from werkzeug.security import check_password_hash

class User(db.Model):
    __tablename__ = 'users'

    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(150), unique=True, nullable=False)
    password_hash = db.Column(db.String(200), nullable=False)

    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
    
    # Method to compare given password with the hashed one
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

The check_password method is designed to validate user-entered passwords by comparing them against the hashed password stored within the user model.

  • Password Validation: It uses check_password_hash to perform a secure comparison, ensuring that the hashed value of the provided password matches the one stored in the database.
  • Return Value: The method returns True if the passwords match, allowing successful authentication, or False if they do not match, indicating an authentication failure.

Implementing User Login Service

Now, let's dive into the login functionality. We'll use the UserService to also handle the logic of authenticating a user with their credentials.

Here's the relevant function in app/services/user_service.py:

from models.user import User, db

class UserService:
 
    # Register method...   

    # Login method
    @staticmethod
    def login(username, password):
        # Retrieve user instance using its username
        user = User.query.filter_by(username=username).first()
        # Use the check_password method to compare the passwords
        if user and user.check_password(password):
            # Return the user object if login is successful
            return user
        # Return None if login fails due to invalid credentials
        return None

This function contains the logic needed for user authentication:

  • User Fetching: We fetch the user based on the username.
  • Password Checking: If the user exists, we verify their password using the check_password method.
  • Return Value: If both checks pass, the user object is returned. Otherwise, None is returned to indicate unsuccessful login.

Creating User Login Route

Next, we'll implement the route that handles user login. This involves creating a route to process login requests and manage user sessions.

Let’s include it to app/controllers/user_controller.py:

from flask import Blueprint, render_template, request, redirect, url_for, flash, session
from services.user_service import UserService

user_service = UserService()

user_controller = Blueprint('user', __name__)

# Routes for rendering the template and registration...

# Route for handling login
@user_controller.route('/login', methods=['POST'])
def login():
    # Retrieve username and password from the registration form
    username = request.form['username']
    password = request.form['password']
    # Try to login the user using the provided username and password
    user = user_service.login(username, password)
    # # If successful, add user.id to session and redirect to the list page
    if user:
        session['user_id'] = user.id
        return redirect(url_for('todo.list_todos'))
    # If it fails, add failure flash message and redirect to auth page
    else:
        flash("Invalid username or password.")
        return redirect(url_for('user.auth_page'))

Let's break it down:

  • Data Collection: First, we capture the username and password from the login form.
  • Authentication: Next, we use the login function from UserService to verify credentials.
  • Session Management: On successful login, the user’s ID is stored in the session to maintain their login state.
  • Feedback to User: Finally, we provide feedback with flash messages, redirecting users accordingly based on login success.

Updating Middleware to Include Login Route

With the login functionality now implemented in the controller, let's update the authentication middleware to ensure users can access the login route without being logged in.

Add 'user.login' to the open routes in app/middlewares/authentication_middleware.py:

from flask import session, redirect, url_for, request

def require_login_middleware(app):
    @app.before_request
    def require_login():
        # Include 'user.login' to the accessible endpoints
        if request.endpoint not in ['user.auth_page', 'user.register', 'user.login', 'static']:
            if 'user_id' not in session:
                return redirect(url_for('user.auth_page'))

This update allows users to reach the login endpoint unauthenticated.

Updating the Auth Template

Now, let's ensure that our auth.html template has a defined action for the login button to direct requests to the correct route. This will enable the form to send a POST request to the appropriate endpoint for logging in. Here's how the relevant section of the auth.html file should look:

<!-- Form for registration and login -->
<form method="POST">
    <label for="username">Username:</label>
    <input type="text" id="username" name="username" required><br><br>

    <label for="password">Password:</label>
    <input type="password" id="password" name="password" required><br><br>

    <!-- Buttons to either register or login -->
    <button type="submit" formaction="{{ url_for('user.login') }}">Login</button>
    <button type="submit" formaction="{{ url_for('user.register') }}">Register</button>
</form>

The login button now directs the form submission to the user.login route while the register button routes to user.register, managing authentication and registration individually.

User Flow Overview

To better understand how users interact with the login system in your Flask ToDo app, let's go through the flow step-by-step:

  1. Registration: A new user registers by providing a unique username and password. This information is securely stored in the database with the password hashed for security. Once registered, the user can proceed to log in.

  2. Login: After registration, the user submits their username and password through the login form. The application processes the request, checking the credentials against the stored data.

  3. Authentication: If the login credentials are correct, the user is authenticated. The app sets a session variable to keep the user logged in throughout their interaction with the app.

  4. Access to Routes: Once logged in, the user can try to access any route or feature in the app. The middleware now recognizes the user as authenticated, so it will not block or stop access to protected routes, allowing seamless navigation across the app's functionalities.

This flow ensures a secure and smooth user experience, guiding users from registration through login and authentication processes seamlessly.

Summary and Practice Section

In this lesson, we’ve successfully implemented a login system for your Flask ToDo app, building on the previous registration setup. You now understand how to authenticate users through login credentials and secure sessions to provide access to protected routes.

As you move on to the hands-on practice exercises, reinforce these concepts and experiment with them. You're doing great—keep it up!

Sign up

Join the 1M+ learners on CodeSignal

Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal