Skip to content

Lab 9: User Registration System (Part 1)

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.


  • Create a user database table with proper schema design.
  • Build a UserModel with 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 AuthController extending BaseController.
  • Apply the Result pattern for validation feedback.
  • Prevent common security vulnerabilities (SQL injection, XSS).

  • 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.

  1. Open phpMyAdmin in your browser
  2. Select your project database
  3. Click on the “SQL” tab
  4. Execute the following SQL statement:
SQL Statement to Create the Users Table
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
);
  1. Click “Go” to execute the query
  2. 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'.


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.


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
}

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
}

Create app/Views/auth/register.php:

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:

This method handles GET requests and displays the registration form. Render the auth/register.php view with an appropriate page title.

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.
}

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' method

  1. Visit http://localhost/[your-app]/register
  2. Fill in all fields with valid data
  3. Click “Register”
  4. You should see a success message and be redirected to the login page

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.”
  1. Register a new user
  2. Open phpMyAdmin and browse the users table
  3. Check the password_hash column: it should contain a long string starting with $2y$ (bcrypt), not the plain-text password


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