Tutorial Setting Up a Secure Password Management System
Secure Login System with PHP and MySQL
In this tutorial, I'll teach you how to create your very own secure PHP login system. A login form is what your website's visitors can use to login to your website to access restricted content, such as a profile page. We will leverage MySQL to retrieve account data from the database and validate captured data with PHP.
The Advanced package includes additional features and a download link to the source code. In addition, it includes the complete tutorial source code.
1. Getting Started
There are a few steps we need to take before we create our secure login system. We need to set up our web server environment and ensure we have the required extensions enabled.
1.1. Requirements
- If you haven't got a local web server set-up, I recommend you download and install XAMPP.
- XAMPP is a cross-platform web server package that includes the essentials for front-end and back-end developers. It includes PHP, MySQL, Apache, phpMyAdmin, and more. It's unnecessary to install all the software separately with XAMPP, especially while working on a development environment.
1.2. What You Will Learn in this Tutorial
- Form Design Design an elegant login form with HTML5 and CSS3.
- Prepared SQL Queries How to properly prepare SQL queries to prevent SQL injection and therefore prevent your database from being exposed.
- Basic Validation Validating form data that is sent to the server using GET and POST requests (username, password, email, etc.).
- Session Management Initialize sessions and store retrieved database results. Sessions are saved on the server and are associated with a unique ID that is saved in the browser (cookie).
1.3. File Structure & Setup
We can now start our web server and create the files and directories we're going to use for our login system.
- Open XAMPP Control Panel
- Next to the Apache module click Start
- Next to the MySQL module click Start
- Navigate to XAMPP's installation directory (C:xampp)
- Open the htdocs directory
- Create the following directories and files:
File Structure
-- phplogin|-- index.html|-- style.css|-- authenticate.php|-- logout.php|-- home.php|-- profile.php
Each file will consist of the following:
- index.html The login form created with HTML5 and CSS3. We don't need to use PHP in this file. Therefore, we can save it as plain HTML.
- style.css The stylesheet (CSS3) for our secure login system.
- authenticate.php Authenticate users, connect to the database, validate form data, retrieve database results, and create new sessions.
- logout.php Destroy the logged-in sessions and redirect the user to the login page.
- home.php Basic home page for logged-in users.
- profile.php Retrieve the user's account details from our MySQL database and populate them with PHP and HTML.
2. Creating the Login Form Design
We will now create a form that our users can use to enter their details and submit them for processing. We will utilize HTML and CSS for this part of the tutorial, as PHP will not be necessary on this page.
Edit the index.html file with your favorite code editor and add the following code:
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Login</title><link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.7.1/css/all.css"></head><body><div class="login"><h1>Login</h1><form action="authenticate.php" method="post"><label for="username"><i class="fas fa-user"></i></label><input type="text" name="username" placeholder="Username" id="username" required><label for="password"><i class="fas fa-lock"></i></label><input type="password" name="password" placeholder="Password" id="password" required><input type="submit" value="Login"></form></div></body></html>
If we navigate to the index page in our web browser, it should resemble the following:
http://localhost/phplogin/index.html
Pretty basic, right? Let's edit our style.css file and implement code that will improve the appearance of the form.
Add the following code to the style.css file:
* { box-sizing: border-box; font-family: -apple-system, BlinkMacSystemFont, "segoe ui", roboto, oxygen, ubuntu, cantarell, "fira sans", "droid sans", "helvetica neue", Arial, sans-serif; font-size: 16px;}body { background-color: #435165;}.login { width: 400px; background-color: #ffffff; box-shadow: 0 0 9px 0 rgba(0, 0, 0, 0.3); margin: 100px auto;}.login h1 { text-align: center; color: #5b6574; font-size: 24px; padding: 20px 0 20px 0; border-bottom: 1px solid #dee0e4;}.login form { display: flex; flex-wrap: wrap; justify-content: center; padding-top: 20px;}.login form label { display: flex; justify-content: center; align-items: center; width: 50px; height: 50px; background-color: #3274d6; color: #ffffff;}.login form input[type="password"], .login form input[type="text"] { width: 310px; height: 50px; border: 1px solid #dee0e4; margin-bottom: 20px; padding: 0 15px;}.login form input[type="submit"] { width: 100%; padding: 15px; margin-top: 20px; background-color: #3274d6; border: 0; cursor: pointer; font-weight: bold; color: #ffffff; transition: background-color 0.2s;}.login form input[type="submit"]:hover {background-color: #2868c7; transition: background-color 0.2s;}
We've implemented a variety of CSS properties that'll make our login form look more appealing. Feel free to customize them.
We need to include our new stylesheet in our index.html file and therefore we must add the following code to the head section:
<link href="style.css" rel="stylesheet" type="text/css">
And now, if we refresh the index.html page in our web browser, our login form will look more appealing:
http://localhost/phplogin/index.html
That looks much better! Let's narrow down the form elements so we can better understand what's going on.
- Form We need to implement both the action and post attributes. The action attribute will be set to the authentication file, as when the form is submitted, the form data will be sent to the authentication file for processing. In addition, the method attribute is declared as post as this will enable us to process the form data using the POST request method.
- Input (text/password) We need to name our form fields so the server can recognize them. The value of the attribute name we can declare as username, which we can use to retrieve the post variable in our authentication file to get the data, for example: $_POST['username'].
- Input (submit) On form submission, the data will be sent to our authentication file for processing.
3. Creating the Database and setting-up Tables
For this part, you will need to access your MySQL database, either using phpMyAdmin or your preferred MySQL database management application.
Follow the below instructions if you're using phpMyAdmin.
- Navigate to: http://localhost/phpmyadmin/
- Click the Databases tab at the top
- Under Create database, enter phplogin in the text box
- Select utf8_general_ci as the collation
- Click Create
You can use your own database name, but for this tutorial, we'll use phplogin.
Essentially, we require an accounts table, as this will store all the accounts (usernames, passwords, emails, etc.) that are registered with the system.
Click the database on the left side panel (phplogin) and execute the following SQL statement:
CREATE TABLE IF NOT EXISTS `accounts` (`id` int(11) NOT NULL AUTO_INCREMENT, `username` varchar(50) NOT NULL, `password` varchar(255) NOT NULL, `email` varchar(100) NOT NULL, PRIMARY KEY (`id`)) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;INSERT INTO `accounts` (`id`, `username`, `password`, `email`) VALUES (1, 'test', '$2y$10$SfhYIDtn.iOuCW7zfoFLuuZHX6lja4lF4XA4JqNmpiH/.P3zB8JCa', '[email protected]');
On phpMyAdmin this should look like:
http://localhost/phpmyadmin/
The above SQL statement code will create the accounts table with the columns id, username, password, and email.
The SQL statement will insert a test account with the username: test, and the password: test. The test account will be used for testing purposes to ensure our login system is functioning correctly.
4. Authenticating Users with PHP
Now that we have our database set up, we can go ahead and start coding with PHP. We're going to start with the authentication file, which will process and validate the form data that we'll send from our index.html file.
Edit the authenticate.php file and add the following:
<?phpsession_start();// Change this to your connection info.$DATABASE_HOST = 'localhost';$DATABASE_USER = 'root';$DATABASE_PASS = '';$DATABASE_NAME = 'phplogin';// Try and connect using the info above.$con = mysqli_connect($DATABASE_HOST, $DATABASE_USER, $DATABASE_PASS, $DATABASE_NAME);if ( mysqli_connect_errno() ) {// If there is an error with the connection, stop the script and display the error.exit('Failed to connect to MySQL: ' . mysqli_connect_error());}
Initially, the code will start the session as this enables us to preserve account details on the server and will be used later on to remember logged-in users. Without sessions, we can't associate the client with the server.
Connecting to the database is essential. Without it, how can we retrieve and store information related to our users? Therefore, we must make sure to update the variables to reflect our MySQL database credentials.
Add below:
// Now we check if the data from the login form was submitted, isset() will check if the data exists.if ( !isset($_POST['username'], $_POST['password']) ) {// Could not get the data that should have been sent.exit('Please fill both the username and password fields!');}
The above code will ensure the form data exists, whereas if the user tries to access the file without submitting the form, it will output a simple error.
Add below:
// Prepare our SQL, preparing the SQL statement will prevent SQL injection.if ($stmt = $con->prepare('SELECT id, password FROM accounts WHERE username = ?')) {// Bind parameters (s = string, i = int, b = blob, etc), in our case the username is a string so we use "s"$stmt->bind_param('s', $_POST['username']);$stmt->execute();// Store the result so we can check if the account exists in the database.$stmt->store_result();$stmt->close();}?>
The above code will prepare the SQL statement that will select the id and password columns from the accounts table. In addition, it will bind the username to the SQL statement, execute it, and then store the result.
Tip Leveraging prepared statements correctly will secure your SQL queries and therefore prevent SQL injection.
After the following line:
$stmt->store_result();
Add:
if ($stmt->num_rows > 0) {$stmt->bind_result($id, $password);$stmt->fetch();// Account exists, now we verify the password.// Note: remember to use password_hash in your registration file to store the hashed passwords.if (password_verify($_POST['password'], $password)) {// Verification success! User has logged-in!// Create sessions, so we know the user is logged in, they basically act like cookies but remember the data on the server.session_regenerate_id();$_SESSION['loggedin'] = TRUE;$_SESSION['name'] = $_POST['username'];$_SESSION['id'] = $id;echo 'Welcome back, ' . htmlspecialchars($_SESSION['name'], ENT_QUOTES) . '!';} else {// Incorrect passwordecho 'Incorrect username and/or password!';}} else {// Incorrect usernameecho 'Incorrect username and/or password!';}
First, we must check if the query has returned any results. If the username doesn't exist in the database, then there would be no results.
If the username exists, we can bind the results to both the $id and $password variables.
Subsequently, we proceed to verify the password with the password_verify function. Only passwords that were created with the password_hash function will work.
If you don't want to use any password encryption method, you can simply replace the following code:
if (password_verify($_POST['password'], $password)) {
With:
if ($_POST['password'] === $password) {
However, I don't recommend removing the hashing functions because if somehow your database becomes exposed, all the passwords stored in the accounts table will also be exposed. In addition, the user will have a sense of privacy knowing their password is encrypted. It's good practice to hash passwords.
Upon successful authentication from the user, session variables will be initialized and preserved until they're destroyed by either logging out or the session expiring. These session variables are stored on the server and are associated with a session ID stored in the user's browser. We'll use these variables to determine whether the user is logged in or not and to associate the session variables with our retrieved MySQL database results.
Did you know?The session_regenerate_id() function will help prevent session hijacking as it regenerates the user's session ID that is stored on the server and as a cookie in the browser.
The user cannot change the session variables in their browser, and therefore you don't need to be concerned about such a matter. The only variable they can change is the encrypted session ID, which associates the user with the server sessions. A valid SSL certificate and forcing HTTPS are vital to prevent session hijacking.
Now, we can test the login system and make sure the authentication works correctly. Navigate to http://localhost/phplogin/index.html in your browser.
Type in a random username and password, and click the login button. It should output an error that should look like the following:
http://localhost/phplogin/authenticate.php
Don't worry, it's not broken! If we navigate back to our login form and enter test for both the username and password fields, the authentication page will look like the following:
http://localhost/phplogin/authenticate.php
If you receive an error, make sure to double-check your code to make sure you haven't missed anything or check if the test account exists in your database.
5. Creating the Home Page
The home page will be the first page our users see when they've logged in. The only way they can access this page is if they're logged in, whereas if they aren't, they will be redirected back to the login page.
Edit the home.php file and add the following code:
<?php// We need to use sessions, so you should always start sessions using the below code.session_start();// If the user is not logged in redirect to the login page...if (!isset($_SESSION['loggedin'])) {header('Location: index.html');exit;}?>
Basically, the above code will check if the user is logged in. If they are not, they will be redirected to the login page. Remember the $_SESSION['loggedin'] variable we defined in the authenticate.php file? This is what we can use to determine whether users are logged in or not.
Coding Tip The isset() function will check if a particular variable is declared and therefore prevent such errors from occurring, especially when dealing with HTTP methods.
After, we can add some HTML to our home page. Below the closing tag, add the following code:
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Home Page</title><link href="style.css" rel="stylesheet" type="text/css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer"></head><body class="loggedin"><nav class="navtop"><div><h1>Website Title</h1><a href="profile.php"><i class="fas fa-user-circle"></i>Profile</a><a href="logout.php"><i class="fas fa-sign-out-alt"></i>Logout</a></div></nav><div class="content"><h2>Home Page</h2><p>Welcome back, <?=htmlspecialchars($_SESSION['name'], ENT_QUOTES)?>!</p></div></body></html>
The above code is the template for our home page. On this page, the user will encounter a welcome message along with their name being displayed, which utilizes the session variables we declared in the authentication file. Awesome, right?
We need to add CSS for the home page. Add the following code to style.css file:
.navtop {background-color: #2f3947;height: 60px;width: 100%;border: 0;}.navtop div {display: flex;margin: 0 auto;width: 1000px;height: 100%;}.navtop div h1, .navtop div a {display: inline-flex;align-items: center;}.navtop div h1 {flex: 1;font-size: 24px;padding: 0;margin: 0;color: #eaebed;font-weight: normal;}.navtop div a {padding: 0 20px;text-decoration: none;color: #c1c4c8;font-weight: bold;}.navtop div a i {padding: 2px 8px 0 0;}.navtop div a:hover {color: #eaebed;}body.loggedin {background-color: #f3f4f7;}.content {width: 1000px;margin: 0 auto;}.content h2 {margin: 0;padding: 25px 0;font-size: 22px;border-bottom: 1px solid #e0e0e3;color: #4a536e;}.content > p, .content > div {box-shadow: 0 0 5px 0 rgba(0, 0, 0, 0.1);margin: 25px 0;padding: 25px;background-color: #fff;}.content > p table td, .content > div table td {padding: 5px;}.content > p table td:first-child, .content > div table td:first-child {font-weight: bold;color: #4a536e;padding-right: 15px;}.content > div p {padding: 5px;margin: 0 0 10px 0;}
Now that we have our home page set up, we can redirect our users from the authenticate.php file to our home page, edit authenticate.php and replace the following line of code:
echo 'Welcome back, ' . htmlspecialchars($_SESSION['name'], ENT_QUOTES) . '!';
With:
header('Location: home.php');
If you log in with the test account, you should see something like this:
http://localhost/phplogin/home.php
This is a pretty basic home page. You can customize it to how you want now that you understand how it works.
6. Creating the Profile Page
The profile page will display the account information for the logged-in user.
Edit the profile.php file and add the following code:
<?php// We need to use sessions, so you should always start sessions using the below code.session_start();// If the user is not logged in redirect to the login page...if (!isset($_SESSION['loggedin'])) {header('Location: index.html');exit;}$DATABASE_HOST = 'localhost';$DATABASE_USER = 'root';$DATABASE_PASS = '';$DATABASE_NAME = 'phplogin';$con = mysqli_connect($DATABASE_HOST, $DATABASE_USER, $DATABASE_PASS, $DATABASE_NAME);if (mysqli_connect_errno()) {exit('Failed to connect to MySQL: ' . mysqli_connect_error());}// We don't have the password or email info stored in sessions, so instead, we can get the results from the database.$stmt = $con->prepare('SELECT password, email FROM accounts WHERE id = ?');// In this case we can use the account ID to get the account info.$stmt->bind_param('i', $_SESSION['id']);$stmt->execute();$stmt->bind_result($password, $email);$stmt->fetch();$stmt->close();?>
The above code retrieves additional account information from the database, as before with the home page, we didn't need to connect to the database because we retrieved the data stored in sessions.
We're going to populate all the account information for the user and therefore we must retrieve the password and email columns from the database. We don't need to retrieve the username or id columns because we've them stored in session variables that were declared in the authenticate.php file.
After the closing tag, add the following code:
<!DOCTYPE html><html><head><meta charset="utf-8"><title>Profile Page</title><link href="style.css" rel="stylesheet" type="text/css"><link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.2.0/css/all.min.css" integrity="sha512-xh6O/CkQoPOWDdYTDqeRdPCVd1SpvCA9XXcUnZS2FmJNp1coAFzvtCN9BmamE+4aHK8yyUHUSCcJHgXloTyT2A==" crossorigin="anonymous" referrerpolicy="no-referrer"></head><body class="loggedin"><nav class="navtop"><div><h1>Website Title</h1><a href="profile.php"><i class="fas fa-user-circle"></i>Profile</a><a href="logout.php"><i class="fas fa-sign-out-alt"></i>Logout</a></div></nav><div class="content"><h2>Profile Page</h2><div><p>Your account details are below:</p><table><tr><td>Username:</td><td><?=htmlspecialchars($_SESSION['name'], ENT_QUOTES)?></td></tr><tr><td>Password:</td><td><?=htmlspecialchars($password, ENT_QUOTES)?></td></tr><tr><td>Email:</td><td><?=htmlspecialchars($email, ENT_QUOTES)?></td></tr></table></div></div></body></html>
A simple layout that will populate account information. If you navigate to the profile.php file, it will look like the following:
http://localhost/phplogin/profile.php
Remember, the passwords are encrypted, so you cannot see the decrypted password unless you create a new session variable and store the password in the authenticate.php file.
7. Creating the Logout Script
Creating the logout script is straightforward. All you need to do is destroy the sessions that were declared in the authenticate.php file.
Edit the logout.php file and add the following code:
<?phpsession_start();session_destroy();// Redirect to the login page:header('Location: index.html');?>
Initialize sessions, destroy them, and redirect the user to the login page. We use sessions to determine whether the user is logged in or not, so by removing them, the user will not be logged in.
Additional Tips and Resources
Further increase security with our tips and resources below.
- Always use the htmlspecialchars() function to escape user input.
- Place the connection details inside a single file that's outside of the webroot directory to further increase security.
- Secure Session INI Settings: https://www.php.net/manual/en/session.security.ini.php
- Never use XAMPP for production purposes because it's not designed for such.
- Always use HTTPS and have a dedicated SSL certificate.
- Use PHP's error_reporting(0) in production to suppress error messages and log errors to a file or database for review by developers.
Conclusion
You should now have a basic understanding of how a login system works with PHP and MySQL. You're free to use the source code and incorporate it into your own projects.
The next step is to create a registration system that will enable visitors to register.
If you've enjoyed reading this tutorial, don't forget to follow us and share the article, as it will help us create future tutorials and update existing content with new features. We greatly appreciate the support!
Next tutorial in this series: Secure Registration System with PHP and MySQL
If you would like to support us, consider purchasing the advanced secure login & registration system below as it will greatly help us create more tutorials and keep our website up and running. The advanced package includes improved code and more features.