Lab 9: User Registration System (Part 1)
Resources
Section titled “Resources”Overview
Section titled “Overview”In this lab, you will implement a secure user registration system that allows customers to create accounts. You will build the database schema, a UserModel for data operations, validation logic, and a registration form with proper security measures including password hashing.
Part 2 covers login, sessions, middleware, and role-based access control.
Learning Objectives
Section titled “Learning Objectives”- Create a user database table with proper schema design.
- Build a
UserModelwith CRUD operations. - Implement secure password hashing using
password_hash(). - Validate user input (required fields, email format, uniqueness, password strength).
- Create a registration form with proper HTML structure.
- Build an
AuthControllerextendingBaseController. - Apply the Result pattern for validation feedback.
- Prevent common security vulnerabilities (SQL injection, XSS).
Prerequisites
Section titled “Prerequisites”- Working Slim 4 application with BaseController pattern.
- Flash Messages lab completed (FlashMessage helper).
- MySQL database connection using PDOService.
- BaseModel class available.
- Bootstrap CSS included in views.
- Access to phpMyAdmin for database management.
Step 1: Create the Users Database Table
Section titled “Step 1: Create the Users Database Table”- Open phpMyAdmin in your browser
- Select your project database
- Click on the “SQL” tab
- Execute the following SQL statement:
CREATE TABLE users ( id INT AUTO_INCREMENT PRIMARY KEY, first_name VARCHAR(100) NOT NULL, last_name VARCHAR(100) NOT NULL, username VARCHAR(50) UNIQUE NOT NULL, email VARCHAR(100) UNIQUE NOT NULL, password_hash VARCHAR(255) NOT NULL, role ENUM('admin', 'customer') DEFAULT 'customer', created_at DATETIME DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP);- Click “Go” to execute the query
- Verify the table was created by clicking on the “Structure” tab or running
DESCRIBE users;
The UNIQUE constraints on email and username prevent duplicate accounts. VARCHAR(255) for password_hash handles bcrypt/Argon2 hashes. The ENUM restricts role values to 'admin' or 'customer'.
Step 2: Create the UserModel Class
Section titled “Step 2: Create the UserModel Class”Step 2.1: Class Structure
Section titled “Step 2.1: Class Structure”Create a UserModel class in the app/Domain/Models/ directory. This model handles all database operations related to users. Follow the same pattern as ProductsModel: use the namespace App\Domain\Models, extend BaseModel, and add a constructor that accepts PDOService and passes it to the parent.
Step 2.2: Implement createUser() Method
Section titled “Step 2.2: Implement createUser() Method”This method inserts a new user with a hashed password. Never store passwords in plain text. If the database is compromised, hashed passwords still protect users.
public function createUser(array $data): int{ // TODO: Hash the password before storing it
// TODO: Insert the user into the database
// TODO: Return the new user's ID}Step 2.3: Implement findByEmail() Method
Section titled “Step 2.3: Implement findByEmail() Method”public function findByEmail(string $email): ?array{ // TODO: Look up a single user by their email address // Return the user data or null if not found}Step 2.4: Implement findByUsername() Method
Section titled “Step 2.4: Implement findByUsername() Method”public function findByUsername(string $username): ?array{ // TODO: Look up a single user by their username // Return the user data or null if not found}Step 2.5: Implement Existence Check Methods
Section titled “Step 2.5: Implement Existence Check Methods”These methods check whether an email or username is already taken during registration.
public function emailExists(string $email): bool{ // TODO: Check whether a user with this email already exists in the database}
public function usernameExists(string $username): bool{ // TODO: Check whether a user with this username already exists in the database}Step 3: Create the Registration Form View
Section titled “Step 3: Create the Registration Form View”Create app/Views/auth/register.php:
<?php
use App\Helpers\ViewHelper;
ViewHelper::loadHeader($title);?>
<div class="row justify-content-center"> <div class="col-md-6"> <div class="card"> <div class="card-header"> <h3 class="text-center">Create Account</h3> </div> <div class="card-body"> <form method="POST" action="<?= APP_BASE_URL ?>/register"> <div class="mb-3"> <label for="first_name" class="form-label">First Name</label> <input type="text" class="form-control" id="first_name" name="first_name" required> </div>
<div class="mb-3"> <label for="last_name" class="form-label">Last Name</label> <input type="text" class="form-control" id="last_name" name="last_name" required> </div>
<div class="mb-3"> <label for="username" class="form-label">Username</label> <input type="text" class="form-control" id="username" name="username" required> </div>
<div class="mb-3"> <label for="email" class="form-label">Email Address</label> <input type="email" class="form-control" id="email" name="email" required> </div>
<div class="mb-3"> <label for="password" class="form-label">Password</label> <input type="password" class="form-control" id="password" name="password" required> <div class="form-text"> Password must be at least 8 characters long and contain at least one number. </div> </div>
<div class="mb-3"> <label for="confirm_password" class="form-label">Confirm Password</label> <input type="password" class="form-control" id="confirm_password" name="confirm_password" required> </div>
<div class="d-grid gap-2"> <button type="submit" class="btn btn-primary">Register</button> </div> </form>
<div class="mt-3 text-center"> <p>Already have an account? <a href="<?= APP_BASE_URL ?>/login">Login here</a></p> </div> </div> </div> </div></div>
<?php ViewHelper::loadFooter(); ?>Step 4: Create AuthController with Registration Logic
Section titled “Step 4: Create AuthController with Registration Logic”Create app/Controllers/AuthController.php. Follow the same pattern as your other controllers: extend BaseController and inject UserModel via the constructor.
The controller needs two methods:
Step 4.1: register() Method
Section titled “Step 4.1: register() Method”This method handles GET requests and displays the registration form. Render the auth/register.php view with an appropriate page title.
Step 4.2: store() Method
Section titled “Step 4.2: store() Method”This method handles POST requests and processes the registration form submission.
public function store(Request $request, Response $response, array $args): Response{ // TODO: Extract the submitted form fields from the request
// TODO: Validate all fields: // - All fields are required // - Email must be a valid format // - Email must not already be registered // - Username must not already be taken // - Password must be at least 8 characters and contain at least one number // - Password and confirmation must match // Collect any validation errors into an array.
// TODO: If validation fails, display the first error as a flash message // and redirect to the registration page
// TODO: Build the user data array and hardcode the role as 'customer'. // Create the user using the UserModel. // On success, display a success flash message and redirect to the login page. // On failure, display an error flash message and redirect to the registration page.}Step 5: Register Routes
Section titled “Step 5: Register Routes”Open app/Routes/web-routes.php and add:
use App\Controllers\AuthController;
// TODO: Create a GET route for '/register' that maps to AuthController::class 'register' method// Set the route name to 'auth.register'
// TODO: Create a POST route for '/register' that maps to AuthController::class 'store' methodTesting Your Implementation
Section titled “Testing Your Implementation”Valid Registration
Section titled “Valid Registration”- Visit
http://localhost/[your-app]/register - Fill in all fields with valid data
- Click “Register”
- You should see a success message and be redirected to the login page
Validation Errors
Section titled “Validation Errors”Test each validation rule by submitting the form with:
- Empty fields: should show “All fields are required.”
- Invalid email (e.g., “notanemail”): should show “Invalid email format.”
- Duplicate email (register twice with the same email): should show “Email already registered.”
- Duplicate username: should show “Username already taken.”
- Short password (less than 8 characters): should show “Password must be at least 8 characters long.”
- Password without a number (e.g., “password”): should show “Password must contain at least one number.”
- Mismatched passwords: should show “Passwords do not match.”
Password Hashing Verification
Section titled “Password Hashing Verification”- Register a new user
- Open phpMyAdmin and browse the
userstable - Check the
password_hashcolumn: it should contain a long string starting with$2y$(bcrypt), not the plain-text password
Test Your Understanding
Section titled “Test Your Understanding”Next Steps
Section titled “Next Steps”Continue to Part 2 to implement:
- User login with password verification
- Session management
- Route protection with middleware
- Role-based access control (admin vs customer)
- Dashboard views