We're now ready to enhance our Flask ToDo application by adding user registration functionality. In the previous lesson, we laid the groundwork with the authentication middleware. Now, let's take it a step further and allow users to sign up and create accounts.
The user registration process is crucial for letting new users onboard into our application. It safely stores their credentials and prepares them for future logins. By the end of this lesson, we'll collectively integrate user registration into our Flask app using models, services, and controllers.
Let's start by creating the file for our User
model, which will store user-related data. We'll head over to app/models/
and create a new file named user.py
.
Python1from models import db 2from werkzeug.security import generate_password_hash 3 4class User(db.Model): 5 __tablename__ = 'users' 6 7 # Define the primary key for the User table 8 id = db.Column(db.Integer, primary_key=True) 9 # Define a column for username, ensuring it's unique and cannot be null 10 username = db.Column(db.String(150), unique=True, nullable=False) 11 # Define a column to store the hashed password, not storing it as plain text 12 password_hash = db.Column(db.String(200), nullable=False) 13 14 def set_password(self, password): 15 # Convert the plain password to a hashed password for security 16 self.password_hash = generate_password_hash(password)
Let's break it down:
- Data Storage: Our
User
model is a representation of the user data structure. It includes anid
for unique user identification, ausername
which is set to be unique and required, and apassword_hash
that stores the user's password securely in a hashed format. - Security: With
generate_password_hash
, we convert plain text passwords into hashed passwords, which are more secure and protect user credentials from being easily read in case of a database breach.
Hashing involves transforming data, such as passwords, into a fixed-size string of characters—a hash code. This method enhances security by storing the hash instead of the plain text version, making it significantly more challenging for attackers to retrieve the original data.
Werkzeug is a Python library that simplifies web development by offering various utilities, including secure password hashing. It employs the PBKDF2 algorithm, which uses salting and multiple iterations to create strong hashes resistant to attacks.
To incorporate Werkzeug in your Flask app, you can install it with the following command:
Bash1pip install werkzeug
Next, we'll create a service to handle the user registration logic. In app/services/
, let's create a file named user_service.py
with the following code:
Python1from models.user import User, db 2 3class UserService: 4 @staticmethod 5 def register(username, password): 6 # Check if a user with the given username already exists in the database 7 existing_user = User.query.filter_by(username=username).first() 8 if existing_user: 9 # If user exists, return None indicating registration failure 10 return None 11 # Create and setup a new User object with the provided username 12 new_user = User(username=username) 13 # Hashes the password and stores it securely in the user object 14 new_user.set_password(password) 15 # Add the new user object to the database session for saving 16 db.session.add(new_user) 17 # Commit the transaction to save the new user to the database 18 db.session.commit() 19 # Return the new user object indicating successful registration 20 return new_user
Here's what this does:
- User Existence: The service first checks if there's an existing user with the same username in the database. This prevents duplicate registrations and ensures each username is unique.
- Registration Process: If no user exists with the provided username, a new
User
instance is created. The password is hashed and securely stored, and the new user object is added to the database and committed as a transaction. This returns the new user if registration is successful.
Now let's move on to the user_controller
. The controller file was already set up in our last lesson, so we'll just expand it to include the new registration route.
Python1from flask import Blueprint, render_template, request, redirect, url_for, flash 2from services.user_service import UserService 3 4user_service = UserService() 5 6user_controller = Blueprint('user', __name__) 7 8# Route to render the authentication page 9@user_controller.route('/auth', methods=['GET']) 10def auth_page(): 11 return render_template('auth.html') 12 13# New route for handling user registration 14@user_controller.route('/register', methods=['POST']) 15def register(): 16 # Retrieve username and password from the form 17 username = request.form['username'] 18 password = request.form['password'] 19 # Try to register the user using the provided username and password 20 user = user_service.register(username, password) 21 # If successful, add successful flash message and redirect to auth page 22 if user: 23 flash("Registration successful! Please log in.") 24 return redirect(url_for('user.auth_page')) 25 # If it fails, add failure flash message and redirect to auth page 26 else: 27 flash("User already exists.") 28 return redirect(url_for('user.auth_page'))
Here's what we did:
- Form Handling: The
register
route processes form input for username and password, passing them to the registration service. - Feedback: Using Flask's
flash
function, we notify users about the success or failure of their registration. If registration is successful, the user is prompted to log in; if not, they're informed that the username is already taken.
After adding the registration route in the controller, it's important to update our authentication middleware to make sure users can access this route without needing to be logged in. We do this by ensuring the route is included in the accessible endpoints.
In app/middlewares/authentication_middleware.py
, we need to add 'user.register' to the list of open routes:
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.register' to the accessible endpoints 7 if request.endpoint not in ['user.auth_page', 'user.register', 'static']: 8 if 'user_id' not in session: 9 return redirect(url_for('user.auth_page'))
By listing 'user.register' here, we allow users to access the registration page without being logged in, ensuring a seamless registration process.
Finally, in the auth.html
template, we'll add flash messages and define an action for the register button to improve user feedback and interactions:
HTML, XML1<!DOCTYPE html> 2<html lang="en"> 3<head> 4 <meta charset="UTF-8"> 5 <meta name="viewport" content="width=device-width, initial-scale=1.0"> 6 <title>Login / Register</title> 7 <link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}"> 8</head> 9<body> 10 <h1>Login or Register</h1> 11 12 <!-- Display flash messages (for errors or successful registration) --> 13 {% with messages = get_flashed_messages() %} 14 {% if messages %} 15 <ul> 16 {% for message in messages %} 17 <li>{{ message }}</li> 18 {% endfor %} 19 </ul> 20 {% endif %} 21 {% endwith %} 22 23 <!-- Form for registration and login --> 24 <form method="POST"> 25 <label for="username">Username:</label> 26 <input type="text" id="username" name="username" required><br><br> 27 28 <label for="password">Password:</label> 29 <input type="password" id="password" name="password" required><br><br> 30 31 <!-- Buttons to either register or login --> 32 <button type="submit">Login</button> 33 <!-- Button to submit the form for registering a new user --> 34 <button type="submit" formaction="{{ url_for('user.register') }}">Register</button> 35 </form> 36</body> 37</html>
Here's what we've done:
- Flash Messages: We integrated flash message display to keep users informed about their actions' results, such as registration success or conflict.
- Register Button Action: The register button is configured with a
formaction
that directs form submissions specifically to theuser.register
route, separating the registration logic from login.
Together, we've integrated user registration into our Flask ToDo application. Here's what we've accomplished:
- We created and explored the
User
model for secure storage. - We implemented the
UserService
class for registration logic. - We expanded the
user_controller
with a new registration route. - We updated the authentication middleware to include the registration route, ensuring users can access it without being logged in.
- We enhanced the
auth.html
template to integrate flash messages for user feedback and set up the registration button to route correctly.
As you proceed to the hands-on practice exercises, you'll reinforce and apply these concepts to strengthen your understanding. Well done on reaching this milestone!