Ready to build a secure PHP login system from scratch? This beginner-friendly PHP login tutorial guides you step-by-step in creating a robust login form using PHP and MySQL. You'll learn how to protect user access to restricted content, like profile pages, by securely retrieving account data from your MySQL database and validating inputs with PHP.

If you're a beginner wondering how to make a PHP login page or how PHP and MySQL work together for user logins, you're in the right place! This PHP login tutorial breaks everything down into easy-to-follow steps, ensuring you grasp the core concepts of creating a secure login from scratch.

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 with Your PHP MySQL Login System

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.php
  |-- style.css
  |-- authenticate.php
  |-- logout.php
  |-- home.php
  |-- profile.php

Each file will consist of the following:

  • index.php — The login form created with HTML and CSS. Also, we'll include validation checks to make sure the user isn't already logged in.
  • style.css — The stylesheet (CSS) 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

Let's start by crafting the login form itself - this is what your users will see and interact with. We'll use simple HTML for the structure and CSS to make it look presentable. No PHP is needed for just the visual design of the form at this stage of our PHP login tutorial.

Edit the index.php file with your favorite code editor and add the following code:

PHP
<?php
// We need to use sessions, so you should always initialize sessions using the below function
session_start();
// If the user is logged in, redirect to the home page
if (isset($_SESSION['account_loggedin'])) {
    header('Location: home.php');
    exit;
}
?>
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,minimum-scale=1">
        <title>Login</title>
    </head>
    <body>
        <div class="login">

            <h1>Member Login</h1>

            <form action="authenticate.php" method="post" class="form login-form">

                <label class="form-label" for="username">Username</label>
                <div class="form-group">
                    <svg class="form-icon-left" width="14" height="14" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M224 256A128 128 0 1 0 224 0a128 128 0 1 0 0 256zm-45.7 48C79.8 304 0 383.8 0 482.3C0 498.7 13.3 512 29.7 512H418.3c16.4 0 29.7-13.3 29.7-29.7C448 383.8 368.2 304 269.7 304H178.3z"/></svg>
                    <input class="form-input" type="text" name="username" placeholder="Username" id="username" required>
                </div>

                <label class="form-label" for="password">Password</label>
                <div class="form-group mar-bot-5">
                    <svg class="form-icon-left" xmlns="http://www.w3.org/2000/svg" width="14" height="14" viewBox="0 0 448 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M144 144v48H304V144c0-44.2-35.8-80-80-80s-80 35.8-80 80zM80 192V144C80 64.5 144.5 0 224 0s144 64.5 144 144v48h16c35.3 0 64 28.7 64 64V448c0 35.3-28.7 64-64 64H64c-35.3 0-64-28.7-64-64V256c0-35.3 28.7-64 64-64H80z"/></svg>
                    <input class="form-input" type="password" name="password" placeholder="Password" id="password" required>
                </div>

                <button class="btn blue" type="submit">Login</button>

                <p class="register-link">Don't have an account? <a href="register.php" class="form-link">Register</a></p>

            </form>

        </div>
    </body>
</html>

If we navigate to the index page in our web browser (localhost/phplogin/index.php), it should resemble the following:

Initial basic HTML structure of the login form for our PHP login system tutorial

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:

CSS
* {
    box-sizing: border-box;
    font-family: system-ui, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";
    font-size: 16px;
}
body, html {
    background-color: #f3f5f7;
    margin: 0;
    padding: 0;
}
h1, h2, h3, h4, h5, h6 {
    margin: 0;
    padding: 0;
    color: #474b50;
}
.form {
    display: flex;
    flex-flow: column;
    width: 100%;
}
.form .form-label {
    display: block;
    padding: 20px 0 10px 0;
    font-weight: 500;
    font-size: 14px;
    color: #474b50;
}
.form .form-group {
    display: flex;
    position: relative;
    justify-content: space-between;
    align-items: center;
    width: 100%;
}
.form .form-group .form-icon-left, .form .form-group .form-icon-right {
    fill: #c1c6cb;
    width: 40px;
    position: absolute;
    transform: translateY(-50%);
    top: 50%;
    pointer-events: none;
}
.form .form-group .form-icon-left {
    left: 0;
}
.form .form-group .form-icon-left + .form-input {
    padding-left: 40px;
}
.form .form-group .form-icon-right {
    right: 0;
}
.form .form-group .form-icon-right + .form-input {
    padding-right: 40px;
}
.form .form-group:focus-within .form-icon-left {
    fill: #989fa8;
}
.form .form-input {
    width: 100%;
    height: 43px;
    border: 1px solid #dee1e6;
    padding: 0 15px;
    border-radius: 4px;
    color: #000;
}
.form .form-input::placeholder {
    color: #989fa8;
}
.form .form-link {
    color: #2a8eeb;
    font-weight: 500;
    text-decoration: none;
    font-size: 14px;
}
.form .form-link:hover {
    color: #136fc5;
}
.form p.register-link {
    margin: 0;
    padding: 20px 0 0 0;
    font-size: 14px;
    color: #6b7179;
}
.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    text-decoration: none;
    appearance: none;
    cursor: pointer;
    border: 0;
    background-color: #3e7bd6;
    color: #FFFFFF;
    padding: 0 14px;
    font-size: 14px;
    font-weight: 600;
    border-radius: 4px;
    height: 42px;
    box-shadow: 0px 0px 6px 1px rgba(45, 57, 68, 0.1);
}
.btn:hover {
    background-color: #3172d3;
}
.login, .register {
    display: flex;
    flex-flow: column;
    width: 400px;
    max-width: 95%;
    background-color: #ffffff;
    box-shadow: 0px 0px 7px 1px rgba(45, 57, 68, 0.05);
    border-radius: 5px;
    margin: 100px auto;
    padding: 35px;
}
.login h1, .register h1 {
    text-align: center;
    font-size: 24px;
    font-weight: 500;
    padding: 15px 0;
    margin: 0;
}
.pad-1 { padding: 5px; }
.mar-1 { margin: 5px; }
.pad-2 { padding: 10px; }
.mar-2 { margin: 10px; }
.pad-3 { padding: 15px; }
.mar-3 { margin: 15px; }
.pad-4 { padding: 20px; }
.mar-4 { margin: 20px; }
.pad-5 { padding: 25px; }
.mar-5 { margin: 25px; }
.pad-bot-1 { padding-bottom: 5px; }
.pad-top-1 { padding-top: 5px; }
.pad-left-1 { padding-left: 5px; }
.pad-right-1 { padding-right: 5px; }
.pad-x-1 { padding-left: 5px; padding-right: 5px; }
.pad-y-1 { padding-top: 5px; padding-bottom: 5px; }
.mar-bot-1 { margin-bottom: 5px; }
.mar-top-1 { margin-top: 5px; }
.mar-left-1 { margin-left: 5px; }
.mar-right-1 { margin-right: 5px; }
.mar-x-1 { margin-left: 5px; margin-right: 5px; }
.mar-y-1 { margin-top: 5px; margin-bottom: 5px; }
.pad-bot-2 { padding-bottom: 10px; }
.pad-top-2 { padding-top: 10px; }
.pad-left-2 { padding-left: 10px; }
.pad-right-2 { padding-right: 10px; }
.pad-x-2 { padding-left: 10px; padding-right: 10px; }
.pad-y-2 { padding-top: 10px; padding-bottom: 10px; }
.mar-bot-2 { margin-bottom: 10px; }
.mar-top-2 { margin-top: 10px; }
.mar-left-2 { margin-left: 10px; }
.mar-right-2 { margin-right: 10px; }
.mar-x-2 { margin-left: 10px; margin-right: 10px; }
.mar-y-2 { margin-top: 10px; margin-bottom: 10px; }
.pad-bot-3 { padding-bottom: 15px; }
.pad-top-3 { padding-top: 15px; }
.pad-left-3 { padding-left: 15px; }
.pad-right-3 { padding-right: 15px; }
.pad-x-3 { padding-left: 15px; padding-right: 15px; }
.pad-y-3 { padding-top: 15px; padding-bottom: 15px; }
.mar-bot-3 { margin-bottom: 15px; }
.mar-top-3 { margin-top: 15px; }
.mar-left-3 { margin-left: 15px; }
.mar-right-3 { margin-right: 15px; }
.mar-x-3 { margin-left: 15px; margin-right: 15px; }
.mar-y-3 { margin-top: 15px; margin-bottom: 15px; }
.pad-bot-4 { padding-bottom: 20px; }
.pad-top-4 { padding-top: 20px; }
.pad-left-4 { padding-left: 20px; }
.pad-right-4 { padding-right: 20px; }
.pad-x-4 { padding-left: 20px; padding-right: 20px; }
.pad-y-4 { padding-top: 20px; padding-bottom: 20px; }
.mar-bot-4 { margin-bottom: 20px; }
.mar-top-4 { margin-top: 20px; }
.mar-left-4 { margin-left: 20px; }
.mar-right-4 { margin-right: 20px; }
.mar-x-4 { margin-left: 20px; margin-right: 20px; }
.mar-y-4 { margin-top: 20px; margin-bottom: 20px; }
.pad-bot-5 { padding-bottom: 25px; }
.pad-top-5 { padding-top: 25px; }
.pad-left-5 { padding-left: 25px; }
.pad-right-5 { padding-right: 25px; }
.pad-x-5 { padding-left: 25px; padding-right: 25px; }
.pad-y-5 { padding-top: 25px; padding-bottom: 25px; }
.mar-bot-5 { margin-bottom: 25px; }
.mar-top-5 { margin-top: 25px; }
.mar-left-5 { margin-left: 25px; }
.mar-right-5 { margin-right: 25px; }
.mar-x-5 { margin-left: 25px; margin-right: 25px; }
.mar-y-5 { margin-top: 25px; margin-bottom: 25px; }

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.php file and therefore we must add the following code to the head section:

HTML
<link href="style.css" rel="stylesheet" type="text/css">

And now, if we refresh the index.php page in our web browser, our login form will look more appealing:

HTML layout for the user login form in our PHP login tutorial

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

Every login system needs a place to store user information, and that's where our MySQL database comes in! You'll need to access your MySQL database to set things up. Most beginners use phpMyAdmin (often included with XAMPP) for this PHP MySQL login setup, but any MySQL management tool will do.

Follow the below instructions if you're using phpMyAdmin.

  • In the XAMPP control panel, click Admin next to MySQL
  • Wait a moment until phpMyAdmin appears in your browser (localhost/phpmyadmin)
  • Click the Databases tab at the top
  • Under Create database, enter phplogin in the text box
  • Select utf8mb4_unicode_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 register with the system.

Click the database on the left side panel (phplogin) and execute the following SQL statement:

SQL
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,
	`registered` datetime NOT NULL,
    PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;

INSERT INTO `accounts` (`id`, `username`, `password`, `email`, `registered`) VALUES (1, 'test', '$2y$10$SfhYIDtn.iOuCW7zfoFLuuZHX6lja4lF4XA4JqNmpiH/.P3zB8JCa', 'test@example.com', '2025-01-01 00:00:00');

In phpMyAdmin, this should resemble the following:

Database table schema for a secure PHP login system

The above SQL statement code will create the accounts table with the columns id, username, password, email, and registered.

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. Secure PHP Login: Authenticating Users with PHP & MySQL

With our database ready, it's time for the exciting part: PHP! We'll write the code that actually checks if the username and password are correct for our PHP login system. This is called authentication, and it happens in a dedicated PHP file that processes the login details submitted from our index.php form.

Edit the authenticate.php file and add the following PHP code:

PHP
<?php
// Start the session
session_start();
// Change the below variables to reflect your MySQL database details
$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);
// Check for connection errors
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!');
}

First things first in our PHP script, we initialize PHP sessions. Think of sessions as a way for the website to remember a user as they move from page to page after logging in. Without them, users would have to re-enter their details for every new page in your secure PHP login system!

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:

PHP
// 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:

PHP
// Prepare our SQL, which 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();
    
    // ADD THE REMAINING CODE HERE

    // Close the prepared statement
    $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.

Replace the following line:

// ADD THE REMAINING CODE HERE

With:

PHP
// Check if account exists with the input username
if ($stmt->num_rows > 0) {
    // Account exists, so bind the results to variables
    $stmt->bind_result($id, $password);
    $stmt->fetch();
    // Note: remember to use password_hash in your registration file to store the hashed passwords
    if (password_verify($_POST['password'], $password)) {
        // Password is correct! User has logged in!
        // Regenerate the session ID to prevent session fixation attacks
        session_regenerate_id();
        // Declare session variables (they basically act like cookies but the data is remembered on the server)
        $_SESSION['account_loggedin'] = TRUE;
        $_SESSION['account_name'] = $_POST['username'];
        $_SESSION['account_id'] = $id;
        // Output success message
        echo 'Welcome back, ' . htmlspecialchars($_SESSION['account_name'], ENT_QUOTES) . '!';
        exit;
    } else {
        // Incorrect password
        echo 'Incorrect username and/or password!';
    }
} else {
    // Incorrect username
    echo '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:

PHP
if (password_verify($_POST['password'], $password)) {

With:

PHP
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 the index.php page in your browser.

Type in a random username and password, and click the login button. It should output an error that should resemble the following:

Authentication Incorrect Username 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 resemble the following:

Successful login message after PHP authentication in the secure login system

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 is the first thing users see after successfully logging into your PHP login system. It's a protected area - only logged-in users can get here. If someone tries to sneak in without logging in, our PHP script will send them right back to the login form.

Edit the home.php file and add the following code:

PHP
<?php
// We need to use sessions, so you should always initialize sessions using the below function
session_start();
// If the user is not logged in, redirect to the login page
if (!isset($_SESSION['account_loggedin'])) {
    header('Location: index.php');
    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['account_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:

PHP
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,minimum-scale=1">
        <title>Home</title>
        <link href="style.css" rel="stylesheet" type="text/css">
    </head>
    <body>

        <header class="header">

            <div class="wrapper">

                <h1>Website Title</h1>
                
                <nav class="menu">
                    <a href="home.php">Home</a>
                    <a href="profile.php">Profile</a>
                    <a href="logout.php">
                        <svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M377.9 105.9L500.7 228.7c7.2 7.2 11.3 17.1 11.3 27.3s-4.1 20.1-11.3 27.3L377.9 406.1c-6.4 6.4-15 9.9-24 9.9c-18.7 0-33.9-15.2-33.9-33.9l0-62.1-128 0c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l128 0 0-62.1c0-18.7 15.2-33.9 33.9-33.9c9 0 17.6 3.6 24 9.9zM160 96L96 96c-17.7 0-32 14.3-32 32l0 256c0 17.7 14.3 32 32 32l64 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-64 0c-53 0-96-43-96-96L0 128C0 75 43 32 96 32l64 0c17.7 0 32 14.3 32 32s-14.3 32-32 32z"/></svg>
                        Logout
                    </a>
                </nav>

            </div>

        </header>

        <div class="content">

            <div class="page-title">
                <div class="wrap">
                    <h2>Home</h2>
                    <p>Welcome back, <?=htmlspecialchars($_SESSION['account_name'], ENT_QUOTES)?>!</p>
                </div>
            </div>

            <div class="block">

                <p>This is the home page. You are logged in!</p>

            </div>
            
        </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 code for our remaining pages. Add the following code to the style.css file:

PHP
.header {
    background-color: #333941;
    height: 60px;
    width: 100%;
}
.header .wrapper {
    display: flex;
    justify-content: space-between;
    align-items: center;
    position: relative;
    margin: 0 auto;
    width: 900px;
    height: 100%;
}
.header .wrapper h1, .header .wrapper a {
    display: inline-flex;
    align-items: center;
}
.header .wrapper h1 {
    font-size: 20px;
    padding: 0;
    margin: 0;
    color: #fff;
    font-weight: normal;
}
.header .wrapper .menu {
    display: flex;
    align-items: center;
}
.header .wrapper .menu a {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 32px;
    padding: 0 12px;
    margin: 0 3px;
    text-decoration: none;
    color: #dddfe2;
    font-weight: 500;
    font-size: 16px;
    line-height: 16px;
}
.header .wrapper .menu a svg {
    fill: #dddfe2;
    margin: 2px 8px 0 0;
}
.header .wrapper .menu a:hover, .header .wrapper .menu a:active {
    color: #ebebec;
}
.header .wrapper .menu a:hover svg, .header .wrapper .menu a:active svg {
    fill: #ebebec;
}
.header .wrapper .menu a:last-child {
    margin-right: 0;
}
.content {
    width: 900px;
    margin: 0 auto;
}
.content .page-title {
    display: flex;
    align-items: center;
    padding: 25px 0 10px 0;
}
.content .page-title h2 {
    margin: 0;
    padding: 0 0 7px 0;
    font-size: 20px;
    font-weight: 600;
    color: #53585e;
}
.content .page-title p {
    margin: 0;
    padding: 0;
    font-size: 14px;
    color: #777e86;
}
.content .block {
    box-shadow: 0px 0px 7px 1px rgba(45, 57, 68, 0.05);
    margin: 25px 0;
    padding: 25px;
    border-radius: 5px;
    background-color: #fff;
}
.content .block p {
    padding: 7px;
    margin: 0;
}
.content .profile-detail {
    display: flex;
    flex-flow: column;
    font-size: 18px;
    padding: 5px 0;
}
.content .profile-detail strong {
    display: block;
    color: #92979e;
    font-size: 14px;
    font-weight: 500;
    margin-bottom: 2px;
}

Now that we have our home page set up, we can redirect our users from the authenticate.php file to our home page, so to do that, we need to edit the authenticate.php file and replace the following line of code:

PHP
echo 'Welcome back, ' . htmlspecialchars($_SESSION['account_name'], ENT_QUOTES) . '!';

With:

PHP
header('Location: home.php');

And now if we log in with the test account, we should see a page that resembles the following:

Screenshot of the user home page after a successful PHP login

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
<?php
// We need to use sessions, so you should always initialize sessions using the below function
session_start();
// If the user is not logged in, redirect to the login page
if (!isset($_SESSION['account_loggedin'])) {
    header('Location: index.php');
    exit;
}
// Change the below variables to reflect your MySQL database details
$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);
// Ensure there are no connection errors
if (mysqli_connect_errno()) {
    exit('Failed to connect to MySQL!');
}
// We don't have the email or registered info stored in sessions so instead we can get the results from the database
$stmt = $con->prepare('SELECT email, registered FROM accounts WHERE id = ?');
// In this case, we can use the account ID to get the account info
$stmt->bind_param('i', $_SESSION['account_id']);
$stmt->execute();
$stmt->bind_result($email, $registered);
$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 directly in PHP sessions.

We're going to populate all the account information for the user and therefore we must retrieve the email and registered 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:

PHP
<!DOCTYPE html>
<html>
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,minimum-scale=1">
        <title>Home</title>
        <link href="style.css" rel="stylesheet" type="text/css">
    </head>
    <body>

        <header class="header">

            <div class="wrapper">

                <h1>Website Title</h1>
                
                <nav class="menu">
                    <a href="home.php">Home</a>
                    <a href="profile.php">Profile</a>
                    <a href="logout.php">
                        <svg width="12" height="12" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512"><!--!Font Awesome Free 6.5.1 by @fontawesome - https://fontawesome.com License - https://fontawesome.com/license/free Copyright 2024 Fonticons, Inc.--><path d="M377.9 105.9L500.7 228.7c7.2 7.2 11.3 17.1 11.3 27.3s-4.1 20.1-11.3 27.3L377.9 406.1c-6.4 6.4-15 9.9-24 9.9c-18.7 0-33.9-15.2-33.9-33.9l0-62.1-128 0c-17.7 0-32-14.3-32-32l0-64c0-17.7 14.3-32 32-32l128 0 0-62.1c0-18.7 15.2-33.9 33.9-33.9c9 0 17.6 3.6 24 9.9zM160 96L96 96c-17.7 0-32 14.3-32 32l0 256c0 17.7 14.3 32 32 32l64 0c17.7 0 32 14.3 32 32s-14.3 32-32 32l-64 0c-53 0-96-43-96-96L0 128C0 75 43 32 96 32l64 0c17.7 0 32 14.3 32 32s-14.3 32-32 32z"/></svg>
                        Logout
                    </a>
                </nav>

            </div>

        </header>

        <div class="content">

            <div class="page-title">
                <div class="wrap">
                    <h2>Profile</h2>
                    <p>View your profile details below.</p>
                </div>
            </div>

            <div class="block">

                <div class="profile-detail">
                    <strong>Username</strong>
                    <?=htmlspecialchars($_SESSION['account_name'])?>
                </div>

                <div class="profile-detail">
                    <strong>Email</strong>
                    <?=htmlspecialchars($email)?>
                </div>

                <div class="profile-detail">
                    <strong>Registered</strong>
                    <?=htmlspecialchars($registered)?>
                </div>

            </div>

        </div>

    </body>
</html>

A simple layout that will populate account information. If you navigate to the profile.php file, it will resemble the following:

Example of a user profile page in a PHP login tutorial

To add more columns, add them to the SQL query and bind them to variables.

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:

PHP
<?php
// Start the session
session_start();
// Destroy the active session, which logs the user out
session_destroy();
// Redirect to the login pag
header('Location: index.php');
exit;
?>

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.
  • Add CSRF tokens to your forms to prevent cross-site request forgery attacks.
  • Always use prepared statements to prevent SQL injection attacks.
  • Use password_hash() and password_verify() to hash passwords.
  • Use session_regenerate_id() to prevent session fixation attacks.

Conclusion

And there you have it! You've just walked through building a fundamental PHP MySQL login system. Hopefully, you now grasp the basics of how user authentication works with PHP and MySQL. Feel free to take this PHP login script and build upon it for your own awesome projects.

The next step is to create a registration system that will enable visitors to register. Next tutorial in this series: Secure Registration System with PHP and MySQL

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!

Thank you for reading, and enjoy coding!

If you would like to support us, consider the advanced secure login & registration system below. It will greatly help us create more tutorials and keep our website up and running. The advanced package includes improved code and more features.

Basic
Advanced
Source Code
Database SQL File
Secure Login & Registration System
Home & Profile Pages
Email Activation Feature
MySQLi, PDO & MVC OOP Versions
Edit Profile Page
Remember Me Feature
Forgot & Reset Password
Account Approval Feature
Deactivate Accounts Feature
Email Notifications
Add-ons Included
— CSRF Protection
— Brute Force Protection
— reCAPTCHA v3 Protection
— Native Captcha Protection
— Two-Factor Authentication
— Google OAuth Login
— Facebook OAuth Login
Admin Panel
— View Dashboard
— Create, edit, and delete accounts
— Search, sort, and filter accounts
— Manage Email Templates
— Edit Settings
— Export & Import Accounts (CSV, JSON, etc.)
PHPMailer Integration
AJAX Integration
Responsive Design (mobile-friendly)
SCSS File
Commented Code
Free updates & support (bugs and minor issues)
User Guide
* Payments are processed with PayPal/Stripe.
* Advanced package also includes the tutorial source and basic package.
* Instant download after payment.
$20.00 $30.00
Credit Card, Google Pay & More
Download
PayPal
Download

To learn more about the advanced package, please visit the Advanced Secure Login & Registration System page.