Lesson 3
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:

Python
1from werkzeug.security import check_password_hash 2 3class User(db.Model): 4 __tablename__ = 'users' 5 6 id = db.Column(db.Integer, primary_key=True) 7 username = db.Column(db.String(150), unique=True, nullable=False) 8 password_hash = db.Column(db.String(200), nullable=False) 9 10 def set_password(self, password): 11 self.password_hash = generate_password_hash(password) 12 13 # Method to compare given password with the hashed one 14 def check_password(self, password): 15 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:

Python
1from models.user import User, db 2 3class UserService: 4 5 # Register method... 6 7 # Login method 8 @staticmethod 9 def login(username, password): 10 # Retrieve user instance using its username 11 user = User.query.filter_by(username=username).first() 12 # Use the check_password method to compare the passwords 13 if user and user.check_password(password): 14 # Return the user object if login is successful 15 return user 16 # Return None if login fails due to invalid credentials 17 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:

Python
1from flask import Blueprint, render_template, request, redirect, url_for, flash, session 2from services.user_service import UserService 3 4user_service = UserService() 5 6user_controller = Blueprint('user', __name__) 7 8# Routes for rendering the template and registration... 9 10# Route for handling login 11@user_controller.route('/login', methods=['POST']) 12def login(): 13 # Retrieve username and password from the registration form 14 username = request.form['username'] 15 password = request.form['password'] 16 # Try to login the user using the provided username and password 17 user = user_service.login(username, password) 18 # # If successful, add user.id to session and redirect to the list page 19 if user: 20 session['user_id'] = user.id 21 return redirect(url_for('todo.list_todos')) 22 # If it fails, add failure flash message and redirect to auth page 23 else: 24 flash("Invalid username or password.") 25 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:

Python
1from flask import session, redirect, url_for, request 2 3def require_login_middleware(app): 4 @app.before_request 5 def require_login(): 6 # Include 'user.login' to the accessible endpoints 7 if request.endpoint not in ['user.auth_page', 'user.register', 'user.login', 'static']: 8 if 'user_id' not in session: 9 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:

HTML, XML
1<!-- Form for registration and login --> 2<form method="POST"> 3 <label for="username">Username:</label> 4 <input type="text" id="username" name="username" required><br><br> 5 6 <label for="password">Password:</label> 7 <input type="password" id="password" name="password" required><br><br> 8 9 <!-- Buttons to either register or login --> 10 <button type="submit" formaction="{{ url_for('user.login') }}">Login</button> 11 <button type="submit" formaction="{{ url_for('user.register') }}">Register</button> 12</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!

Enjoy this lesson? Now it's time to practice with Cosmo!
Practice is how you turn knowledge into actual skills.