Coding a secure login system with php and mysql

Updated: 16-May-2023 / Tags: PHP and MYSQL / Views: 8355 - Author: George

Introduction

In this tutorial, you will learn how to create a secure login system using PHP and MySQL. We are going to build a secure register and login application, which will have:

  • A register page to register the user in the database.
  • A login page to log in the user.
  • A forgot password page, so if the user forgets their password, they can request a new one.
  • And a user account page where the user will be redirected when they log in successfully.
  • Every input field will have proper and secure validation, making it easy to implement the code on a live website.

What you will encounter

Let's quickly see what we are going to encounter in this tutorial.

  1. We are going to connect to a database using the mysqli extension.
  2. We will log any database connection errors in a log file. It's a big security risk if we print connection errors to the user.
  3. We will use MySQL prepared statements when querying the database, so we don't need to worry about any SQL injection attacks.
  4. We will hash the password before registering the user in the database.
  5. When the user forgets their password, we will reset it in the database and send them the new one.
  6. We will use the reliable PHPMailer class library to ensure that our emails are delivered.
  7. When the user logs in successfully, they will be redirected to their account page.
  8. In the account page, the user has the option to log out or delete the account.
  9. And we will achieve all our tasks by writing and using functions.

Live demo

Below, I have a live demo of the login system that we are going to code.

You can create a dummy account and play around with the demo. Make some errors, try to break the application.

If you enter an existing email you can check out the forgot password action. Don't worry your email will not be shared or used. In fact it will be deleted a few days later or so.

Go to the login page and log in to your dummy account. Then logout or delete your account. Try out all the actions, you will have a better understanding on what we are going to code.

I hope you tried out the live example. Now, let's start building the application and take a look at the files we need.

Project's folder

We need the following nine files to build our login system.

\-- root
  |-- index.php		
  |-- login.php		
  |-- forgot-password.php
  |-- account.php
  |-- delete-message.php
  |-- db-log.txt
  |-- config.php
  |-- functions.php
  |-- styles.css
  |-- PHPMailer/
  • index.php This will be our starting page and will contain the register form.
  • login.php Here, we will have the login form..
  • forgot-password.php In this file, we will handle the forgot-password form.
  • account.php In a successful login action this is the page that the user will be redirected to.
  • delete-message.php When the user deletes their account, they will be redirected to the delete-message.php page, which will contain a message confirming the account deletion.
  • db-log.txt This file will log any database connection errors that may occur.
  • config.php In this file, we will store both our database connection details and the PHPMailer configurations.
  • function.php This file will contain all the functions needed for coding the login system.
  • style.css We have a CSS file to enhance the visual appearance of our application.
  • And last we have the PHPMailer folder.
    Download PHPMailer from Github, and add it to the project's folder.

These are all the files we need. Now, let's start with our first file, which is the config.php file.

The config file

In the config.php file, we will store the database connection details and the mail settings needed for the PHPMailer library. This file will be included in the functions.php file.

<?php 
	session_start();
	// Mysql server settings. --------------------------

	// Fill in your server's name.
	define('SERVER', 'localhost'); 

	// Fill in your database username.
	define('USERNAME', 'your-mysql-username'); 
	
	// Fill in your database password if any.
	define('PASSWORD', 'your-mysql-password'); 
	
	// Fill in your database.
	define('DATABASE', 'db-name'); 

	// PHPMailer settings. ---------------------------------
	define('MAIL_HOST', 'mail.domain.com');
	define('MAIL_USERNAME', 'info@domain.com');
	define('MAIL_PASSWORD', 'email-account-password');
	
	// Global variables -------------------------------------
	define('WEBSITE_NAME', 'Digitalfox-tutorials');
  • In line 2, we initiate a session because later, when we log in the user, we will store their username in a session.
  • Next, in lines 6, 9, 12, and 15, we define the server, the MySQL username, the MySQL password, and the database that we are going to use. We can utilize the SERVER, USERNAME, PASSWORD, and DATABASE constants anywhere in our PHP code, even inside functions, as constants are global variables.
  • Set your smtp server like `mail.domain.com` or `smtp.mailserver.com`.
    define('MAIL_HOST', 'mail.domain.com');
    Example: If you want to send emails through Gmail the syntax is `smtp.gmail.com`. If you you want to send emails from your website the syntax is `mail.yourWebsite.com`.
  • Set the email address you use to log in to your email account.
    Note: The email address must belong to the above MAIL_HOST.
    define('MAIL_USERNAME', 'info@domain.com');
  • Set the password you use to log in to the above email account.
    define('MAIL_PASSWORD', 'email-account-password');
    Important: If you use Gmail as your MAIL_HOST, you need to enable 2-factor authentication and create an App password to use here instead of your actual Google password.

    Check out the Google Docs guide on how to create a Google App password. How to create a google app password

    If you are struggling with how to set up PHPMailer, I have an article and a video tutorial on that topic. Send Email in PHP using PHPMailer and Gmail

  • And last in line 23 we define the website's name as a constant, allowing us to use it throughout the application.
    define('WEBSITE_NAME', 'Digitalfox-tutorials');

Alright, we are done with the config.php file. Now, let's take a look at the structure of our database table.

The users database table

In the database, we will have a 'users' table where we register users. The table has four columns: a user_id, a username, an encrypted password, and an email column.

users_id username password email
1 George $2y$10$/P03OqfFtsvzaT88mW0G0uLHVtw6UH somemail@mail.com

You can download the SQL file and insert it into your database by clicking Here

Now, let's begin building the application, starting with the functions.php file, and write the functions we need to make the login system functional.

The functions php file

All the functions needed to make the login system functional will be housed in the functions.php file. Let's take a look at them.

  • Firstly, we'll implement a connect() function responsible for connecting our application to the MySQL server and database.
  • Next, the registerUser() function will handle the user registration process, inserting the user into the database and the 'users' table.
  • For user login functionality, we'll have the loginUser() function.
  • To log out a user, we'll implement the logoutUser() function.
  • In case a user forgets their password, the passwordReset() function will handle the process of resetting and sending a new password.
  • Finally, we'll have the deleteAccount() function for users who decide to close their accounts.
  • As you can see, we'll be developing six functions to ensure the functionality of the login system. Let's get started.

Requiring the config file

The initial step in the functions.php file is to require the config.php file, as we need access to the database connection details.

<php 
require "config.php";

The connect function

The first function we are going to write is the connect() function. As the name implies, the connect function is responsible for establishing a connection between PHP and MySQL.

In summary, the connect function establishes a connection to the MySQL server. If there's an error during the connection, it logs the error details in a file. If the connection is successful, it sets the character set and returns the MySQLi object for use in subsequent database operations.

function connect(){
	// Create a new MySQLi object and establish 
	// a connection using the defined constants from 
	// the config file.
	$mysqli = new mysqli(SERVER, USERNAME, PASSWORD, DATABASE);

	// Check if there is a connection error.
	if($mysqli->connect_errno != 0){
		
		// If an error occurs, retrieve the error details.
		$error = $mysqli->connect_error;

		// Get the current date and time in 
		// a human-readable format.
		$error_date = date("F j, Y, g:i a");

		// Create a message combining the error and the date.
		$message = "{$error} | {$error_date} \r\n";

		// Append the error message to a log file 
		// named 'db-log.txt'.
		file_put_contents("db-log.txt", $message, FILE_APPEND);
		
		// Return false to indicate a connection failure.
		return false;
	}else{
		// If the connection is successful, 
		// set the character set to "utf8mb4" which 
		// supports a wider range of characters. 
		$mysqli->set_charset("utf8mb4");

		// Return the MySQLi object, indicating 
		// a successful connection.
		return $mysqli;	
	}
}

The registerUser function

Next we are going to write the registerUser() function. The function takes as parameters the email, the username, the password, and the confirm-password values that come from the register form that we are going to write in the index.php file.

function registerUser($email, $username, $password, $confirm_password) {
    // Establish a database connection.
    $mysqli = connect();
    
    // Trim whitespace from input values.
    $email = trim($email);
    $username = trim($username);
    $password = trim($password);
    $confirm_password = trim($confirm_password);

    // Check if any field is empty.
    $args = func_get_args();
    foreach ($args as $value) {
        if (empty($value)) {
            // If any field is empty, return an error message.
            return "All fields are required";
        }
    }

    // Check for disallowed characters (< and >).
    foreach ($args as $value) {
        if (preg_match("/([<|>])/", $value)) {
            // If disallowed characters are found, 
            // return an error message.
            return "< and > characters are not allowed";
        }
    }

    // Validate email format.
    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        // If email is not valid, return an error message.
        return "Email is not valid";
    }

    // Check if the email already exists in the database.
    $stmt = $mysqli->prepare("SELECT email FROM users WHERE email = ?");
    $stmt->bind_param("s", $email);
    $stmt->execute();
    $result = $stmt->get_result();
    $data = $result->fetch_assoc();
    if ($data != NULL) {
        // If email already exists, return an error message.
        return "Email already exists";
    }

    // Check if the username is too long.
    if (strlen($username) > 100) {
        // If username is too long, return an error message.
        return "Username is too long";
    }

    // Check if the username already exists in the database.
    $stmt = $mysqli->prepare("SELECT username FROM users WHERE username = ?");
    $stmt->bind_param("s", $username);
    $stmt->execute();
    $result = $stmt->get_result();
    $data = $result->fetch_assoc();
    if ($data != NULL) {
        // If username already exists, return an error message.
        return "Username already exists, please use a different username";
    }

    // Check if the password is too long.
    if (strlen($password) > 255) {
        // If password is too long, return an error message.
        return "Password is too long";
    }

    // Check if the passwords match.
    if ($password != $confirm_password) {
        // If passwords don't match, return an error message.
        return "Passwords don't match";
    }

    // Hash the password for security.
    $hashed_password = password_hash($password, PASSWORD_DEFAULT);

    // Insert user data into the 'users' table.
    $stmt = $mysqli->prepare("INSERT INTO users (username, password, email) VALUES (?, ?, ?)");
    $stmt->bind_param("sss", $username, $hashed_password, $email);
    $stmt->execute();

    // Check if the insertion was successful.
    if ($stmt->affected_rows != 1) {
        // If an error occurred during insertion, return an error message.
        return "An error occurred. Please try again";
    } else {
        // If successful, return a success message.
        return "success";
    }
}

Let's explain the registerUser() function.

The initial step in the function is to connect to the MySQL server and the database. We will use the connect() function and store the returned mysqli object in the $mysqli variable.

$mysqli = connect();

Now we have access to the methods and properties of the mysqli object.

Next we will trim any white space from the beginning and the end of every argument.

$email = trim($email);
$username = trim($username);
$password = trim($password);
$confirm_password = trim($confirm_password);

The next step is to check if all fields have values.

$args = func_get_args();
foreach ($args as $value) {
	if(empty($value)){
		return "All fields are required";
	}
}
  • In line 50, we use the func_get_args function to retrieve all the function's arguments. The function returns an array, and we store it in the $args variable.

Next, we will iterate through the arguments once again, checking if any value contains open or closing tag characters (<>). This precaution helps prevent the insertion of any script tags into the database, which could pose a security risk.

foreach ($args as $value) {
	if(preg_match("/([<|>])/", $value)){
		return "<> characters are not allowed";
	}
}

We use the preg_match function for this check, and if any value contains these characters, we return an error and halt the function execution at this point.

Next we are going to check if the user has entered a valid email.

if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
	return "Email is not valid";
}

If the email entered is not valid, we will again return an error and the function will stop here.

Next, we will search the database and the 'users' table to determine if the entered email already exists. This check is crucial, as there cannot be two users with the same email address.

$stmt = $mysqli->prepare("SELECT email FROM users WHERE email = ?");
$stmt->bind_param("s", $email);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_assoc();
if($data != NULL){
	return "Email already exists, please use a different username";
}	
  • We are going to use mysql prepared statements to query the database. In fact we will do this in the whole tutorial.
  • In line 74 to 78 we prepare and execute the query. I am not going to go through and explain how prepared statements are working, i have a tutorial about this topic that you can read How to write mysql prepared statements.
  • In line 78 the $data variable is holding the result from our search query. If the $data variable holds the value of NULL this means that the email doesn't exists in the database. But if the $data variable is NOT NULL, that means that the email already exists in the users table and so we return an error and the function stops here.

Next we move to the username, and we will check the length.

if(strlen($username) > 100){
	return "Username is to long";
}

If the username is greater than 100 characters then we return an error, and again the function stops here.

Next we check if the username exists in the users table. We want the username to be unique. We will query the database using a prepared statement, following a similar approach as we did earlier with the email.

$stmt = $mysqli->prepare("SELECT username FROM users WHERE username = ?");
$stmt->bind_param("s", $username);
$stmt->execute();
$result = $stmt->get_result();
$data = $result->fetch_assoc();
if($data != NULL){
	return "Username already exists, please use a different username";
}

If the username exists we return an error and stop the function here.

Next we are gonna move to the password and check its length.

if(strlen($password) > 255){
	return "Password is to long";
}

If the password's length is greater than 255 characters, we will return an error and stop the function here.

Next we are going to check if the password is equal to the confirm password's value.

if($password != $confirm_password){
	return "Passwords don't match";
}

If the two values don't match we return an error and stop the function here.

Next if we get so far without any errors we will encrypt the password before we store it in the users table.

$hashed_password = password_hash($password, PASSWORD_DEFAULT);

If you are not familiar with password encryption, check this out. How to encode and decode JSON data using php

The last thing that we have to do is to insert the user to the users table.

We are going to use again a prepared statement and and if everything goes well we return the string "success", if not we return an error.

$stmt = $mysqli->prepare("INSERT INTO users(username, password, email) VALUES(?,?,?)");
$stmt->bind_param("sss", $username, $hashed_password, $email);
$stmt->execute();
if($stmt->affected_rows != 1){
	return "An error occurred. Please try again";
}else{
	return "success";			
}

Now that's all the code we have in the registerUser function, it was quite extensive, i hope i explained everything right. Now we are going to write a function to log in the user.

The loginUser function

To log the user in, we will create a function named loginUser(). The loginUser function takes two arguments: the user's username (as the first argument) and the user's password (as the second argument)

function loginUser($username, $password) {
    // Establish a database connection.
    $mysqli = connect();

    // Trim leading and trailing whitespaces 
    // from username and password.
    $username = trim($username);
    $password = trim($password);

    // Check if either username or password is empty.
    if ($username == "" || $password == "") {
        return "Both fields are required";
    }

    // Sanitize username and password to prevent SQL injection.
    $username = filter_var($username, FILTER_SANITIZE_STRING);
    $password = filter_var($password, FILTER_SANITIZE_STRING);

    // Prepare SQL statement to select username 
    // and password from users table.
    $sql = "SELECT username, password FROM users WHERE username = ?";
    $stmt = $mysqli->prepare($sql);
    
    // Bind the username parameter to the prepared statement.
    $stmt->bind_param("s", $username);
    
    // Execute the prepared statement to query the database.
    $stmt->execute();
    
    // Get the result set from the executed statement.
    $result = $stmt->get_result();
    
    // Fetch the associative array representing the first
    // row of the result set.
    $data = $result->fetch_assoc();

    // Check if the username exists in the database.
    if ($data == NULL) {
        return "Wrong username or password";
    }

    // Verify the provided password against the 
    // hashed password in the database.
    if (password_verify($password, $data["password"]) == FALSE) {
        return "Wrong username or password";
    } else {
        // If authentication is successful, 
        // set the user session and redirect to account page.
        $_SESSION["user"] = $username;
        header("location: account.php");
        exit();
    }
}

Let's explain the loginUser() function.

  • Database Connection:

    Establishes a database connection using the connect function.
    $mysqli = connect()
  • Trimming and Validation:

    Trims leading and trailing whitespaces from username and password.
    $username = trim($username);
    $password = trim($password);
    				

    Checks if either username or password is empty and returns an error message if true.
    if ($username == "" || $password == ""){ 
    	return "Both fields are required" 
    }

    Sanitizes username and password to prevent SQL injection.
    $username = filter_var($username, FILTER_SANITIZE_STRING);
    $password = filter_var($password, FILTER_SANITIZE_STRING)
    				
  • Database Query:

    Prepares an SQL statement to select username and password from the 'users' table.
    $sql = "SELECT username, password FROM users WHERE username = ?";
    $stmt = $mysqli->prepare($sql);

    Binds the username parameter to the prepared statement.
    $stmt->bind_param("s", $username);

    Executes the prepared statement to query the database.
    $stmt->execute();

    Retrieves the result set from the executed statement.
    $result = $stmt->get_result();

    Fetches the associative array representing the first row of the result set.
    $data = $result->fetch_assoc();
  • Authentication:

    Checks if the username exists in the database.
    if($data == NULL){
    	return "Wrong username or password"; 
    }

    Verifies the provided password against the hashed password in the database.
    if(password_verify($password, $data["password"]) == FALSE){
     return "Wrong username or password"; 
    }
  • Session and Redirect:

    Sets the user session upon successful authentication.
    $_SESSION["user"] = $username;

    Redirects to the account page and terminates the script.
    header("location: account.php"); 
    exit();

The logoutUser function

The logoutUser() function is the simplest one in our functions file. When this function is executed, the user session is destroyed, and the user is then redirected to the login.php page. In your application, you can customize the redirection to any page as needed.

function logoutUser(){
	session_destroy();
	header("location: login.php");
	exit();
}

The next function is the passwordReset function.

The passwordReset function

The passwordReset() function is responsible for resetting the password in the 'users' table and sending the new password to the user. This function accepts a single argument, the user's email address.

function passwordReset($email){
	// Establish a database connection.
	$mysqli = connect();

	// Trim leading and trailing whitespaces from the email.
	$email = trim($email);

	// Validate the email format using the 
	// FILTER_VALIDATE_EMAIL filter.
	if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
		return "Email is not valid";
	}

	// Prepare and execute a query to check if the email 
	// exists in the 'users' table.
	$stmt = $mysqli->prepare("SELECT email FROM users WHERE email = ?");
	$stmt->bind_param("s", $email);
	$stmt->execute();
	$result = $stmt->get_result();
	$data = $result->fetch_assoc();

	// Check if the email exists in the database.
	if($data == NULL){
		return "Email doesn't exist in the database";
	}

	// Generate a new password.
	$str = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcefghijklmnopqrstuvwxyz";
	$password_length = 7;
	$new_pass = substr(str_shuffle($str), 0, $password_length);
	
	// Hash the new password.
	$hashed_password = password_hash($new_pass, PASSWORD_DEFAULT);

	// Create a new PHPMailer instance.
	$mail = new PHPMailer(true);

	// Configure PHPMailer for SMTP mailing.
	$mail->isSMTP();	                  				
	$mail->Host = MAIL_HOST;      		 				
	$mail->SMTPAuth = true;              				
	$mail->Username = MAIL_USERNAME;     				
	$mail->Password = MAIL_PASSWORD;     				
	$mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;  	
	$mail->Port = 587; 

	// Set email recipients, subject, and body.
	$mail->setFrom(MAIL_USERNAME, WEBSITE_NAME);  
	$mail->addAddress($email);					 
	$mail->Subject = 'Password Recovery';
	$mail->Body = "You can log in with your new password : {$new_pass}";

	// Send the email.
	if(!$mail->send()){
		return "Email not send. Please try again";
	}else{
		// Update the user's password in the 'users' table.
		$stmt = $mysqli->prepare("UPDATE users SET password = ? WHERE email = ?");
		$stmt->bind_param("ss", $hashed_password, $email);
		$stmt->execute();
		
		// Check if the password update was successful.
		if($stmt->affected_rows != 1){
			return "There was a connection error, please try again."; 
		}else{
			return "success";
		}
	}
}

Let's explain the passwordReset() function.

  • Database connection and email validation:

    Again we will use the connect function to connect to the MySQL server, and trim any white space left and right from the given email.
    $mysqli = connect();
    $email = trim($email);
    	

    Next we are going to check if the user has entered a valid email.
    if(!filter_var($email, FILTER_VALIDATE_EMAIL)){
    	return "Email is not valid";
    }
    	

    We are gonna use the filter_var function, but this time we use the FILTER_VALIDATE_EMAIL flag. The function returns FALSE if not a valid email structure is given. If this is the case we return an error message.

  • Email Existence Check:

    Next as we always do when we query the database we are going to use a prepared statement to check if the given email exists in the database. If the email doesn't exists in the users table, the $data variable will have a value of NULL. If this is the case we return an error.
    $stmt = $mysqli->prepare("SELECT email FROM users WHERE email = ?");
    $stmt->bind_param("s", $email);
    $stmt->execute();
    $result = $stmt->get_result();
    $data = $result->fetch_assoc();
    if($data == NULL){
    	return "Email doesn't exist in the database";
    }
    		
  • New Password Generation:

    If the email exists, we will generate a random password, which will be seven characters long
    $str = "1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZabcefghijklmnopqrstuvwxyz";
    $password_length = 7;
    $new_pass = substr(str_shuffle($str), 0, $password_length);
    	

    In line 209, we have a string that includes the entire English alphabet (both upper and lower case) and the numbers from 1 to 9.

    In line 210 we are setting the password's length.

    In line 211, we shuffle the string, and then we extract the first seven characters from the shuffled string, storing them in the $new_pass variable.

  • Password Hashing:

    Next we are going to hash the password, so we can update the old password in the database.
    $hashed_password = password_hash($new_pass, PASSWORD_DEFAULT);
    		
  • PHPMailer Configuration:

    Configures PHPMailer for SMTP mailing.
    $mail = new PHPMailer(true);
    $mail->isSMTP();	                  				
    $mail->Host = MAIL_HOST; 
    $mail->SMTPAuth = true;
    $mail->Username = MAIL_USERNAME;
    $mail->Password = MAIL_PASSWORD;
    $mail->SMTPSecure = PHPMailer::ENCRYPTION_STARTTLS;
    $mail->Port = 587;
    		
  • Email Sending:

    Sets email recipients, subject, and body.
    $mail->setFrom(MAIL_USERNAME, WEBSITE_NAME);  
    $mail->addAddress($email);
    $mail->Subject = 'Password Recovery';
    $mail->Body = "You can log in with your new password : {$new_pass}";
    		

    Checks if the email was not sent and returns an error.
    if(!$mail->send()){
    	return "Email not send. Please try again";
    }
    		
  • Password Update in Database:

    Prepares and executes a query to update the user's password in the 'users' table, and checks if the password update was successful.
    $stmt = $mysqli->prepare("UPDATE users SET password = ? WHERE email = ?");
    $stmt->bind_param("ss", $hashed_password, $email);
    $stmt->execute();
    if($stmt->affected_rows != 1){
    	return "There was a connection error, please try again."; 
    }else{
       return "success";
    }
    

    And we are finished with the passwordReset function. Now let's go to write our last function in the file, which is the deleteAccount function.

The deleteAccount function

The deleteAccount() function provides the user with the option to delete their account, essentially removing their username, email, and password from the database.

function deleteAccount(){
	// Establish a database connection.
	$mysqli = connect();

	// Prepare and execute a query to delete the 
	// user's information from the 'users' table.
	$sql = "DELETE FROM users WHERE username = ?";
	$stmt = $mysqli->prepare($sql);
	$stmt->bind_param("s", $_SESSION['user']);
	$stmt->execute();

	// Check if the deletion was successful.
	if($stmt->affected_rows != 1){
		return "An error occurred. Please try again";
	}else{
		// Destroy the user's session.
		session_destroy();

		// Redirect to the delete-message.php page.
		header("location: delete-message.php");
		exit();
	}
}

At this point, we have completed the functions.php file. Now, let's write the HTML pages corresponding to each function. We'll begin with the index.php page, which hosts the register form.

The index page

We re gonna have the register form in the index.php file. When the application runs this will be our first screen.

At the top of every form page, we include a PHP code block that requires the functions.php file. This inclusion provides us with access to the necessary functions throughout the page.

<?php 
	require "functions.php";
	if(isset($_POST['submit'])){
		$response = registerUser($_POST['email'], $_POST['username'], $_POST['password'], $_POST['confirm-password']);
	}
?>

In line 3, we check if the form is submitted. If the form is indeed submitted, we enter the if statement. Within this block, we call the registerUser function, providing it with the necessary arguments: the user's email, username, password, and confirm-password values. The return value of the function is stored in the $response variable.

Next, we present the HTML code for the register form. An important consideration is that we set the input value attributes to the posted values that the $_POST variable holds when the form is submitted. This approach ensures that we retain the entered values in the fields in case of an error, creating what is commonly referred to as a 'sticky form.' The use of the '@' character in front of the $_POSTvariables instructs the browser not to generate a warning when the page loads.

<form action="" method="post">
	<label>Email *</label>
	<input type="text" name="email" value="<?php echo @$_POST['email']; ?>" >

	<label>Username *</label>
	<input type="text" name="username" value="<?php echo @$_POST['username']; ?>" >

	<label>Password *</label>
	<input type="text" name="password" value="<?php echo @$_POST['password']; ?>">

	<label>Confirm Password *</label>
	<input type="text" name="confirm-password" value="<?php echo @$_POST['confirm-password']; ?>">

	<button type="submit" name="submit">Submit</button>
	<?php 
		if(@$response == "success"){
			?>
				<p class="success">Your registration was successful</p>
			<?php
		}else{
			?>
				<p class="error"><?php echo @$response; ?></p>
			<?php
		}
		?>
	</form> <!-- end of register form -->	

In line 21, we have the submit button. The name attribute of the submit button is the value that we check in the PHP code at the top of the page to determine if the form is submitted.

In line 23 we are gonna check the $response variable that the registerUser function returns, and if the value is the string "success", we display a success message. Else we display the error.

Now let's move to the login form

The login form

Again we are going to have our php code-block at the top of the page in which we require the functions.php file so we can have access to the loginUser function.

<?php 
	require "functions.php";
	if(isset($_POST['submit'])){
		$response = loginUser($_POST['username'], $_POST['password']);
	}
?>

Again we are going to check if the form is submitted, and if this is the case we are going to run the userLogin function.

The function takes two arguments the user's username and password. And we store the returned value to the $response variable. We do the same thing here as we did in the register page.

In the login form we set the submitted values that the $_POST variable holds to the value attributes. We did the same thing in the register form, so we not lose the values that we type in when there is an error.

<form action="" method="post">	
	<label>Username</label>
	<input type="text" name="username" value="<?php echo @$_POST['username']; ?>">

	<label>Password</label>
	<input type="text" name="password" value="<?php echo @$_POST['password']; ?>">
	
	<button type="submit" name="submit">Submit</button>
	<p class="error"><?php echo @$response; ?></p>
</form>		

In line 16 we display any error that may occur. Notice that we don't have here a success message. That is because we redirect the user to the account.php page when he logs in successfully.

Next we will write the forgot password form.

The forgot-password form

We have the same pattern here as well. We require the functions.php file and check if the form is submitted.

<?php 
	require "functions.php";
	if(isset($_POST['submit'])){
		$response = passwordReset($_POST['email']);
	}
?>

If the form is submitted, we are gonna run this time the passwordReset function. The function takes as a single argument the user's email address.

In the forgot password form we have only one field and that is the email. We set the input's value attribute to the submitted value as we did in the other two forms.

<form action="" method="post">
	<label>Email</label>
	<input type="text" name="email" value="<?php echo @$_POST['email']; ?>">
	
	<button type="submit" name="submit">Submit</button>	

And again we check the $response variable to see if the function succeeded or there was an error.

	<?php 
		if(@$response == "success"){
			?>
				<p class="success">Please go to your email account and use your new password.</p>
			<?php
		}else{
			?>
				<p class="error"><?php echo @$response; ?></p>
			<?php
		}
	?>
</form>

Now let's go to the account.php page.

The user account page

The account.php page is the page that the user is redirected if he logs in successfully. Again we are going to have our php code-block at the top of the page, but it will look a little bit different than the code-block that we had on the other pages.

<?php 
	require "functions.php";
	if(!isset($_SESSION['user'])){
		header("location: login.php");
		exit();
	}

	if(isset($_GET['logout'])){
		logoutUser();
	}
	
	if(isset($_GET['confirm-account-deletion'])){
		deleteAccount();
	}
?>
  • in line 2 we require the functions.php file.
  • In line 3 we have an if statement and we will check if the user is loged-in, else we redirect him to the login.php screen. We are doing this to make sure that the account page is secure. Only if a user is loged-in can have access to this page.
  • In this page the user has the options to perform two actions.
  • The first action is to logout, so in line 8 we are gonna check if the user clicked on a logout link that we are gonna have in the page, and if so, we are going to execute the logoutUser function.
  • The second option for the user is to click on the delete account link. In this case we are going to ask him again if he is sure that he wants to delete his account. And if he confirm, we have an if statement in line 12 to catch the confirmation and to execute the deleteAccount function, in line 13.

Now let's see the account.php file's html code.

<h2>Welcome <?php echo $_SESSION["user"] ?></h2>
<h4>This is a secure page</h4>

<a href="?logout">Logout</a>

<?php 
	if(isset($_GET['delete-account'])){
		?>
			<p class="confirm-deletion">
				Are you sure you want to delete you account?
				<a href="?confirm-account-deletion">Delete account</a>
			</p>
		<?php
	}else{
		?>
			<a href="?delete-account">Delete account</a>
		<?php
	}
?>
  • In line 17 we use the user session to welcome the user.
  • In line 20 we have the logout link. The "?logout" query string means that we send a GET request to the same page. If you go to line 8, you will see that we are capturing the "logout" key to log-out the user.
  • In line 23 we use an if statement that says: If the user clicks on the delete account link on line 32, we show the confirm message. And if the user clicks on the delete account link on line 27, then we delete his account on line 13.
    Hope it makes sense.

The CSS code

In the css file we have the exact same styling rules as you saw in the live demo.

*{
	margin: 0;
	padding: 0;
	box-sizing: border-box;
}

body{
	font-family: sans-serif;
	min-height: 100vh;
	color: #555;
	padding-top: 30px;
}

form, .page{
	max-width: 90%;
	border:  thin solid #e4e4e4;
	padding: 20px 40px;
	border-radius: 5px;
	box-shadow: 0 0 10px rgba(0, 0, 0, 0.15);
	margin: 0 auto;
}

form h2, .page h2{
	font-size: 32px;
	line-height: 52px;
	color: #555;
	margin-bottom: 5px;
}

form h4, .page h4{
	margin-bottom: 30px;
	padding-bottom: 10px;
	border-bottom: thin solid #e4e4e4;
	line-height: 26px;
}

form h4 span{
	color: #af0c0c;
	font-weight: normal;
}

form > div{
	margin-bottom: 20px;
}

form .grid{
	display: grid;
	grid-template-columns: 1fr 1fr;
	grid-column-gap: 50px;
}

form label{
	display: block;
	margin-bottom: 10px;
	padding-left: 5px;
}

form input{
	display: block;
	width:  100%;
	padding: 10px;	
	margin-bottom: 10px;
	font-size: 16px;
	border:  thin solid #e4e4e4;
	margin-bottom: 30px;
	border-radius: 5px;
}

form input:focus
{
	outline: none;
}

form button{
	background: #32749a;
	color: white;
	border: none;	
	padding: 10px 30px;
	font-size: 16px;
	cursor: pointer;
	border-radius: 5px;
	margin-bottom: 10px;	
}

form button:active{
	background-color: green;
}

form p{
	margin-top: 15px;
}

form p a{
	display: inline-block;
	color: #006dbc;
	text-decoration: none;
}

form p a:hover{
	text-decoration: underline;
}

 .page a{
	 display: inline-block;
	 color: #006dbc;
	 margin-top: 20px;	
 }

a.confirm-deletion{
	text-decoration: none;
	color: red;
	margin-left: 10px;
}

.error{
	margin-top: 30px;
	color: #af0c0c;
	line-height: 26px;
}

.success{
	margin-top: 30px;
	color: green;
}

Summary

In this tutorial we saw how to code a secure register and login system using PHP and MySQL.

Last Words

This is a quite lengthy tutorial, please let me know if everything is understandable. Thanks for reading, i hope you find the article helpful.
Please leave a comment if you find any errors, so i can update the page with the correct code.

Source code

If you feel like avoiding all the copying and pasting, you can buy me a coffee and get the source code in a zip file. Get the source code

Buy me a coffee

If you like to say thanks, you can buy me a coffee.

Buy me a coffee with paypal

Comment section

You can leave a comment, it will help me a lot.

Or you can just say hi. 😉