DEV Community

Cover image for Building a Secured User Authentication System with PHP, MySQL, PDO and hashed password
Assia Ettalibi
Assia Ettalibi

Posted on

Building a Secured User Authentication System with PHP, MySQL, PDO and hashed password

Tired of Boring and insecured Logins? Let's ditch the basic sign-up and sign-in systems and build a secure, engaging application with a personalized dashboard using PHP! This tutorial will guide you through creating a user management system with a focus on security best practices and user experience.

What You'll Learn:
How to handle user registration and login.
Securely store passwords using bcrypt hashing.
Implement a basic routing system.
Leverage a service container to manage dependencies.

Prerequisites:

  • Basic knowledge of PHP, HTML forms, and SQL
  • PHP (version 7.4 or higher) installed on your local machine
  • A web server (e.g., Apache, Nginx)
  • A database server (e.g., MySQL)

1. Project Setup:

Begin by creating a new project directory and setting up the basic file structure:

└── 📁App
└── 📁Config : config.php , container.php , Database.php
└── 📁Controller : AuthController.php ,
DashboardController.php
└── 📁Core : app.php
└── 📁Helper : Autholoader.php , validator.php
└── 📁Model : userModel.php
└── 📁Routes : api.php , Router.php
└── 📁services : AuthService.php , container.php
└── 📁View
└── 📁Dashboard : DashboardView.php
     └── 📁Login : Mainview.php , SignInView.php ,
SignUpView.php

warning ! : in this tutorial we gonna see the most important files in our project

2. Database Configuration (config/config.php):

Create a config.php file to store your database credentials and other configuration settings:

Image description

3. Database Interaction (model/UserModel.php):

Image description

The code defines a class UserModel within the namespace App\Model. The class is designed to interact with a database to perform operations related to user management, specifically creating and validating users. Here's a breakdown of the principal components and functionalities:

Namespace and Imports:

namespace App\Model; :
Declares that the UserModel class is part of the App\Model namespace.

use App\Config\DataBase;:
Imports the DataBase class from the App\Config namespace, which presumably handles database connections.

use PDOException;: Imports the PDOException class, which is used to catch exceptions thrown by PDO operations.
Class Definition :

class UserModel: Defines the UserModel class, which encapsulates methods for interacting with the database regarding user data.
Constructor and Destructor :

The constructor (__construct) accepts an instance of DataBase and connects to the database using $this->db::connect();.
The destructor (__destruct) unsets the $db property, effectively closing the database connection when the object is destroyed.
Public Methods :
create($data) :
This method attempts to insert a new user into the users table with the provided data ($data). It uses prepared statements to prevent SQL injection.

It checks for a duplicate entry error (code 23000) and returns an error message if the email already exists in the database.
On success, it returns an array indicating success and the ID of the newly inserted user.

Image description

validateUser($data) (it's inside the class also ):
This method queries the database to find a user with the provided email address.
It uses a prepared statement to safely query the database.
If a user is found, it returns an array indicating success and the user's data.
If no user is found, it returns an array indicating failure.

Error Handling:
Both methods use a try-catch block to catch PDOException exceptions, which are thrown by PDO when there's an error executing a database operation.
In the create method, if a PDOException is caught and its code indicates a duplicate entry error, it returns an error message indicating that the email already exists.

4. Routing (routes/api.php):

Image description

This code defines routes for our application, connecting URLs to specific controller actions.
It uses a Router class to define how different HTTP requests (GET and POST) to specific URLs are handled.
Each route maps a URL to a controller and a method within that controller. For example, a GET request to /login will execute the showLoginForm method of the AuthController.
This allows for organized and efficient handling of requests, directing them to the appropriate code based on the URL and HTTP method.
This is a common way to manage routing in MVC applications, ensuring clear separation between URL handling and application logic.

what is a Router ??
:

Router: The MVC Traffic Cop

In the MVC (Model-View-Controller) architectural pattern, the router acts as a traffic cop, directing incoming requests to the correct controller action. Think of it as the "switchboard" of your application.
Definition:
The router analyzes the URL of a request and determines which controller and action should handle it. It then passes the request to that controller, allowing the MVC flow to continue.
Importance:
Organizes Request Handling: Without a router, it would be chaotic to handle different types of requests. The router ensures each request goes to the right place.

Maintainable Code:
It keeps the code clean and organized by separating URL mapping from application logic.

Flexibility and Scalability:
Routers allow for easy addition of new features and functionalities without major restructuring.

*SEO-Friendliness: Routers can create clean, descriptive URLs that are beneficial for Search Engine Optimization *(SEO).
this is the endspoint of the router :

Image description

5. Dependency Management (Container.php):

By using a Dependency Injection Container, you can:
Decouple components by injecting dependencies instead of hardcoding them.
Easily switch implementations of dependencies (e.g., for testing or different environments).
Improve code organization and testability.

Image description

This code defines a simple Dependency Injection Container in PHP. Here's a breakdown of the important parts:

1. Namespace:
namespace App\Services;: This line indicates that the code belongs to the namespace App\Services. Namespaces help organize code and prevent naming collisions.
2. Class Definition:
class Container { ... }: This defines a class called Container, which will hold and manage dependencies.
3. Bindings Property:
private $bindings = [];: This is a private property that stores the dependencies. It's an array where keys are dependency "identifiers" and values are "factories" responsible for creating those dependencies.
4. set Method:
public function set(string $id, callable $factory): void: This method registers a dependency.
$id: The unique identifier for the dependency (e.g., 'database', 'logger').
$factory: A callable (function or closure) that knows how to create an instance of the dependency.
5. get Method:
public function get(string $id): This method retrieves a dependency by its identifier.
It checks if the dependency is registered using isset($this->bindings[$id]).
If found, it retrieves the $factory associated with the $id.
It then calls the $factory with the container itself as an argument ($factory($this)), allowing the factory to potentially access other dependencies from the container.
Finally, it returns the created instance of the dependency.
If the dependency is not found, it throws an Exception.

6. Views (views/signup.php, views/login.php):

signUp view :

Image description

Image description

)

Image description

Image description

Image description

explication :
1. Namespace:
namespace App\View\Login;: Indicates the code belongs to the namespace App\View\Login, organizing view-related code for login/signup.
2. SignUpView Class:
class SignUpView { ... }: Defines a class named SignUpView, likely responsible for rendering the HTML of the signup form.
3. render Method:
public static function render($errors) { ... }: This static method generates the HTML content of the signup form. The $errors parameter likely contains an array of validation errors from a controller, similar to the login form example.
Key Elements Within the render Method:
Output Buffering:
ob_start(); and ob_get_clean();: Similar to the login view, utilizes output buffering to capture the generated HTML content before sending it to the browser, allowing potential manipulation or processing.
Welcome Message and Login Link:
A simple welcome message is displayed, along with a link to the login page (./login).
Sign-Up Form:
form method="post" action="./register": Defines a form that uses the POST method to submit data to the ./register URL
Input Fields: Creates input fields for user information like "full name," "email," "date of birth," "gender," "password," and "confirm password." Labels are included for clarity.
<div class="--scroll" data-page='0'>: This suggests a potential multi-step form implementation using JavaScript to control form sections based on the data-page attribute.
Error Display:
<?= $errors['full_name'][0] ?? '' ?>: This line, repeated for different fields, uses the null coalescing operator (??) to display the first error message associated with a specific field (e.g., 'full_name') from the $errors array if it exists, otherwise displays nothing.
Navigation Buttons:
<button class="prev hidden" type="button">...: A "previous" button, initially hidden, likely used for multi-step navigation within the form.
<button class="next" type="button"> ...: A "next" button for navigating to the next section of the form.
<button class="submit hidden" type="submit"> ...: A "submit" button, initially hidden, to finalize the registration process. The JavaScript handling the form navigation would likely reveal this button in the last step.

_singIn view : _

Image description

Image description

Image description

Image description

_explication : _ ( " almost the same thing in the signUp"
1. Namespace:
namespace App\View\Login;: Indicates the code belongs to the App\View\Login namespace, keeping login view code organized.
2. SignInView Class:
class SignInView { ... }: Defines a class named SignInView to handle login form rendering.
3. render Method:
public static function render($errors) { ... }: This static method generates the HTML for the login form. The $errors parameter is crucial as it likely contains an array of validation errors passed from the controller.
Key Elements Within the render Method:
Output Buffering:
ob_start(); andob_get_clean();: These functions utilize output buffering. The HTML content is generated and stored in a buffer instead of being immediately sent to the browser. This allows for manipulation or processing of the content before it's displayed.
Welcome Message and Signup Link:
Provides a simple welcome message and a link to the registration page (./register).
Login Form:
form method="post" action="./login": Defines a form that uses the POST method to submit data to the ./login URL .
Input Fields: Creates input fields for "email" and "password," including labels for user clarity.
Error Display:
<?= $errors['null'] ?? '' ?>: Uses the null coalescing operator (??) to display an error message associated with the 'null' key in the $errors array if it exists, otherwise displays nothing. This could be for a general login error.
<?= $errors['email'][0] ?? '' ?> and<?= $errors['password'][0] ?? '' ?>: Similar to the above, these lines display specific error messages associated with the 'email' and 'password' fields, respectively, if they exist in the $errors array.
Sign In Button:
<button class="submit" type="submit">Sign In</button>: Creates a button for users to submit the form.

7.services (AuthService.php ):
Image description

Image description
Image description

Image description

Image description

Image description

Image description

This PHP code defines an AuthServiceclass within the App\Services namespace, which is responsible for handling user registration and login processes, including form validation and password hashing with a salt. Here's a brief explanation focusing on form validation and the salt-hashing process:
Namespace and Imports:
The class is part of the App\Services namespace and imports Validator and UserModel classes from other namespaces.
Class Properties and Constructor :The AuthService class has two private properties: $modelof type UserModel and $validatorof type Validator. These are injected via the constructor, allowing the class to interact with the database and validate data.
Form Validation in validateRegistration:
The validateRegistration method uses the Validator class to validate the registration data. It sets aliases for form fields for better error messages and defines validation rules for each field:
full_name must be required and contain only alphabetic characters.
email must be required and in a valid email format.
dob (date of birth) must be required and in a valid date format.
gender must be required and a valid gender value.
password must be required and meet the criteria defined by the password rule (likely a minimum length and complexity).
confirm_password must match the password field.
If validation fails, it returns an array with success set to false and the errors collected by the validator.
If validation passes, it returns an array with success set to true and the validated data.
Password Hashing with Salt in register :
Before inserting the user data into the database, the register method generates a random salt using random_bytes(16) and converts it to hexadecimal with bin2hex(). This salt is then concatenated with the plain text password.
The concatenated string (salt + password) is hashed using password_hash() with the PASSWORD_ARGON2IDalgorithm, which is a strong hashing algorithm suitable for storing passwords securely.
The hashed password and the generated salt are stored in the database. This ensures that even if the password is compromised, the attacker cannot reverse-engineer the original password from the hash due to the presence of the unique salt.

6. Views (views/dashboardView.php):

dashbord view :

Image description

This code represents a view that generates a dynamic dashboard page. It highlights several important aspects of views in an MVC application:

Image description

the html code :

Image description

Image description

The code represents a simple view in a PHP MVC application, likely designed for a dashboard. Here's a breakdown:
1. Namespace:
namespace App\View\Dashboard;: Similar to the previous example, this indicates the code belongs to the namespace App\View\Dashboard, likely organizing view-related code.
2. SCRIPT_ROOT Constant:
define('SCRIPT_ROOT', 'http://localhost/hackathon');: This defines a constant SCRIPT_ROOT with the base URL of your application. This constant is used throughout the HTML to build paths to resources like CSS files and images.
3. DashboardView Class:
class DashboardView { ... }: This defines a class DashboardView, which likely encapsulates the logic for generating the HTML of the dashboard.
4. render Method:
public static function render() { ... }: This static method generates the actual HTML content of the dashboard. It's designed to be called from a controller, which would pass any necessary data to populate the view.
Key Elements Within the render Method:
HTML Structure: The method outputs a standard HTML document structure, including the

section with metadata and links to CSS stylesheets, and the section containing the main content.
Dynamic Paths: TheSCRIPT_ROOT constant is used to create dynamic paths for resources:
href="<?php echo SCRIPT_ROOT ?>/public/css/style.css"
src="<?php echo SCRIPT_ROOT ?>/public/images/logo.png"
This ensures that resources are loaded correctly regardless of where the application is deployed.
Sidebar and Navbar: The HTML defines a sidebar (<div class="sidebar">) containing navigation links, and a navbar (<div class="navbar">) for search and user information.
PHP Integration: There are some PHP snippets embedded within the HTML:
<h2><?= ucfirst($_SESSION['full_name']) ?></h2>: This line likely displays the user's name retrieved from a session variable.

Image description

Image description

Et Voila ! , this is our "mini-tutorial" for our "mini-project"
to get the full folders and files of our project you can acces to our gitHub : LinkToGithub

Congratulations! You've built a basic PHP authentication system!
Key Security Considerations:
Password Hashing: Always hash passwords using bcrypt (password_hash()) before storing them. Never store passwords in plain text.
Input Validation: Thoroughly validate and sanitize user input to prevent vulnerabilities like SQL injection and cross-site scripting (XSS).
Session Management: Implement secure session management practices to protect user data.
This is a simple starting point. You can expand this system to include password reset functionality, email verification, and more complex authorization rule

Top comments (0)