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.
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
:
Python1from 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, orFalse
if they do not match, indicating an authentication failure.
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
:
Python1from 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.
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
:
Python1from 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
andpassword
from the login form. - Authentication: Next, we use the
login
function fromUserService
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.
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
:
Python1from 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.
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, XML1<!-- 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.
To better understand how users interact with the login system in your Flask ToDo app, let's go through the flow step-by-step:
-
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.
-
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.
-
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.
-
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.
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!