Building a Login System Using MySQL and PHP

Building a Login System Using MySQL and PHP

Building a login system is one of those projects that looks simple from the outside but teaches you a lot once you start writing the code. At first, it feels like all you need is a form, a database, and a button. Then you quickly discover that a real login system needs careful handling of passwords, sessions, user input, and security. That is exactly why it is such a valuable project. It gives you a practical understanding of how PHP talks to MySQL, how authentication works, and how to protect user accounts properly.

In this article, we will build a complete login system using MySQL and PHP step by step. We will start from the database structure, then move to registration, login, session control, logout, and page protection. Along the way, we will use modern PHP practices such as prepared statements and password hashing so the system is not only functional, but also safe and ready for real-world use.

Why build a login system from scratch?

A login system is the foundation of many web applications. Whether you are making a blog, an admin dashboard, an e-commerce site, or a learning platform, authentication is often the first major feature users interact with. When you build it yourself, you gain more than just a working feature. You understand how a user is identified, how sessions persist across pages, and why certain security habits matter so much.

Many beginners make the mistake of storing passwords directly in the database or comparing raw form values without protection. That works in the sense that it may appear to function, but it is dangerously insecure. In a proper login system, passwords must always be hashed, SQL queries must be protected from injection, and access to private pages must be checked carefully. Once you understand these principles, you will be much more confident building any system that requires user accounts.

What we are going to build

We will create a simple but solid authentication system with these parts:

  • A MySQL database table for users

  • A registration page to create new accounts

  • A login page to authenticate users

  • A dashboard page accessible only after login

  • A logout mechanism to end the session

  • Basic protection for private pages

The code will be easy to follow and organized in separate files so you can use it in your own project or expand it later.

Step 1: Create the database

Before writing PHP code, we need a database to store user accounts. Open phpMyAdmin or use MySQL from the command line and create a database, for example:

CREATE DATABASE login_system;

Now select that database and create a users table:

CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    name VARCHAR(100) NOT NULL,
    email VARCHAR(150) NOT NULL UNIQUE,
    password VARCHAR(255) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

This table is simple, but it contains everything we need. The id column gives each user a unique identifier. The name column stores the user’s display name. The email column is used for login and must be unique. The password column stores the hashed password, not the plain password. The created_at column records when the account was created.

Using VARCHAR(255) for the password field is important because hashed passwords can be longer than the original text. Even if today’s hash fits in a smaller field, it is safer to leave enough room.

Step 2: Prepare the project structure

A clean folder structure helps keep the project understandable. You can organize your files like this:

login-system/
│
├── config/
│   └── db.php
├── auth/
│   ├── register.php
│   ├── login.php
│   ├── logout.php
│   └── dashboard.php
├── index.php
└── style.css

This is just one possible structure. You can keep everything in the root folder if you prefer, but separating database configuration and authentication files makes the project easier to maintain.

Step 3: Connect PHP to MySQL

Now let’s create the database connection file. Inside config/db.php, write the following:

<?php
$host = "localhost";
$dbname = "login_system";
$username = "root";
$password = "";

try {
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $username, $password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die("Database connection failed: " . $e->getMessage());
}
?>

This code uses PDO, which is one of the safest and most flexible ways to connect PHP with MySQL. The charset=utf8mb4 part helps support a wide range of characters. The error mode is set to exceptions so connection problems are easier to detect during development.

Some developers use mysqli, and that is also fine. However, PDO gives a clean and modern approach, especially if you want to grow this project later.

Step 4: Build the registration form

A login system is usually useless without user registration. So we need a page where users can create an account first. Create a file named register.php and add this HTML form:

<?php
require_once "../config/db.php";
session_start();

$message = "";

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $name = trim($_POST["name"]);
    $email = trim($_POST["email"]);
    $password = $_POST["password"];

    if (empty($name) || empty($email) || empty($password)) {
        $message = "All fields are required.";
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $message = "Please enter a valid email address.";
    } else {
        $check = $pdo->prepare("SELECT id FROM users WHERE email = ?");
        $check->execute([$email]);

        if ($check->rowCount() > 0) {
            $message = "Email already exists.";
        } else {
            $hashedPassword = password_hash($password, PASSWORD_DEFAULT);

            $stmt = $pdo->prepare("INSERT INTO users (name, email, password) VALUES (?, ?, ?)");
            $stmt->execute([$name, $email, $hashedPassword]);

            $message = "Registration successful. You can now log in.";
        }
    }
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Register</title>
</head>
<body>
    <h2>Create Account</h2>

    <?php if (!empty($message)) : ?>
        <p><?php echo htmlspecialchars($message); ?></p>
    <?php endif; ?>

    <form method="POST" action="">
        <input type="text" name="name" placeholder="Full Name" required>
        <input type="email" name="email" placeholder="Email Address" required>
        <input type="password" name="password" placeholder="Password" required>
        <button type="submit">Register</button>
    </form>
</body>
</html>

This registration logic does several important things. It trims extra spaces from input, checks whether fields are empty, validates the email format, verifies that the email is not already used, and hashes the password before storing it. That last step is one of the most important pieces of the entire system.

Never store the password directly in the database. If your database is ever exposed, plain passwords create a serious security disaster. password_hash() protects users by turning the password into a secure hashed value that cannot be easily reversed.

Why password hashing matters

Passwords should never be stored as plain text. A hashed password is like a one-way fingerprint. PHP gives you built-in functions that make this easy to handle correctly.

When a user creates a password, you hash it before saving it:

$hashedPassword = password_hash($password, PASSWORD_DEFAULT);

When the user logs in later, you compare the entered password with the stored hash:

password_verify($enteredPassword, $storedHash);

This is the correct workflow. You do not decrypt the password because the hash is not meant to be decrypted. Instead, you verify whether the input matches the stored hash.

Step 5: Build the login form

Now let’s create the login page. Create login.php:

<?php
require_once "../config/db.php";
session_start();

$message = "";

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $email = trim($_POST["email"]);
    $password = $_POST["password"];

    if (empty($email) || empty($password)) {
        $message = "Both email and password are required.";
    } else {
        $stmt = $pdo->prepare("SELECT id, name, email, password FROM users WHERE email = ?");
        $stmt->execute([$email]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($user && password_verify($password, $user["password"])) {
            $_SESSION["user_id"] = $user["id"];
            $_SESSION["user_name"] = $user["name"];
            $_SESSION["user_email"] = $user["email"];

            header("Location: dashboard.php");
            exit;
        } else {
            $message = "Invalid email or password.";
        }
    }
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Login</title>
</head>
<body>
    <h2>Login</h2>

    <?php if (!empty($message)) : ?>
        <p><?php echo htmlspecialchars($message); ?></p>
    <?php endif; ?>

    <form method="POST" action="">
        <input type="email" name="email" placeholder="Email Address" required>
        <input type="password" name="password" placeholder="Password" required>
        <button type="submit">Login</button>
    </form>
</body>
</html>

This file fetches the user record using the email address, then checks the password with password_verify(). If the credentials are valid, the script stores user information in the session and redirects to the dashboard.

The session is what keeps the user logged in across multiple pages. Without sessions, each page would forget who the user is the moment they navigate away.

Understanding sessions in PHP

A session is a way to store user-specific data on the server while keeping a session ID in the browser. In simple terms, the browser remembers a key, and the server remembers the user’s login state.

At the top of any file that uses session data, you must call:

session_start();

Without this line, PHP cannot access session variables. Once a user logs in, you can save values like this:

$_SESSION["user_id"] = $user["id"];
$_SESSION["user_name"] = $user["name"];

Then on a protected page, you can check whether those values exist before allowing access.

Step 6: Create a dashboard page

The dashboard is a private page that only logged-in users should see. Create dashboard.php:

<?php
session_start();

if (!isset($_SESSION["user_id"])) {
    header("Location: login.php");
    exit;
}
?>

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Dashboard</title>
</head>
<body>
    <h2>Welcome, <?php echo htmlspecialchars($_SESSION["user_name"]); ?>!</h2>
    <p>You are now logged in successfully.</p>
    <a href="logout.php">Logout</a>
</body>
</html>

This page checks whether the session contains a valid user ID. If not, it redirects the visitor to the login page. This is the core of protecting private content. Even if someone types the dashboard URL directly, they cannot access it unless they are logged in.

The use of htmlspecialchars() is also important. It protects against output-based attacks by ensuring that any special characters are safely displayed as text.

Step 7: Add logout functionality

Logging out is just as important as logging in. When a user is finished, the system should destroy the session and return them to the login page. Create logout.php:

<?php
session_start();
session_unset();
session_destroy();

header("Location: login.php");
exit;
?>

This code clears the session data and destroys the session entirely. After that, the user is no longer authenticated.

Step 8: Improve the login experience

A working login system is good. A pleasant login system is better. Small details matter. For example, showing clear error messages, keeping form layouts clean, and giving users helpful feedback all make the system feel more polished.

You may want to add a link from login to registration and from registration back to login:

<p>Don't have an account? <a href="register.php">Register here</a></p>
<p>Already have an account? <a href="login.php">Login here</a></p>

A basic stylesheet can also make a big difference. Create style.css:

body {
    font-family: Arial, sans-serif;
    background: #f5f5f5;
    margin: 0;
    padding: 0;
}

.container {
    width: 100%;
    max-width: 400px;
    margin: 60px auto;
    background: #fff;
    padding: 30px;
    border-radius: 8px;
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.08);
}

input, button {
    width: 100%;
    padding: 12px;
    margin-top: 10px;
    box-sizing: border-box;
}

button {
    background: #007bff;
    color: #fff;
    border: none;
    cursor: pointer;
}

button:hover {
    background: #0056b3;
}

p {
    margin-top: 15px;
}

Then wrap your form content inside a container:

<div class="container">
    <!-- form here -->
</div>

A nice interface does not make authentication more secure, but it does make the system easier to use. That matters more than many people realize. Users trust systems that feel clear and professional.

Step 9: Add better validation

Basic form validation is important, but a real project should do more than simply check whether fields are empty. You can improve the registration form by enforcing password strength rules, requiring valid email formats, and limiting the length of names.

Here is an example of password validation:

if (strlen($password) < 8) {
    $message = "Password must be at least 8 characters long.";
}

You can also add checks for uppercase letters, lowercase letters, numbers, and special characters if your project requires stronger passwords.

For the name field, you might do:

if (strlen($name) < 2) {
    $message = "Name is too short.";
}

For the email field, the built-in validator works well:

if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
    $message = "Please enter a valid email.";
}

These checks improve data quality and reduce errors later.

Step 10: Secure your queries with prepared statements

One of the biggest mistakes in beginner PHP projects is building SQL queries by directly inserting user input into the statement. That opens the door to SQL injection. Prepared statements solve this problem by separating the query structure from the data.

Unsafe example:

$sql = "SELECT * FROM users WHERE email = '$email'";

Safer example:

$stmt = $pdo->prepare("SELECT * FROM users WHERE email = ?");
$stmt->execute([$email]);

Using prepared statements is one of the easiest ways to protect your database. It should become a habit in every project that touches user input.

Step 11: Protect against common mistakes

A login system is not only about making things work. It is also about avoiding mistakes that weaken the whole application. Some of the most common mistakes include:

Storing passwords in plain text
Using weak or outdated hashing methods
Forgetting to escape output in HTML
Not checking sessions on protected pages
Building SQL queries with direct string concatenation
Allowing users to submit empty values
Forgetting to destroy sessions on logout

Each of these issues can create problems. Some will break functionality, and others will create security holes. The best habit is to write with caution from the beginning rather than fixing mistakes after the system is already in production.

Step 12: Add a password confirmation field

During registration, it is wise to ask the user to enter the password twice. This reduces typos and prevents accidental lockout.

Here is an updated form snippet:

<input type="password" name="password" placeholder="Password" required>
<input type="password" name="confirm_password" placeholder="Confirm Password" required>

And the validation:

$confirm_password = $_POST["confirm_password"];

if ($password !== $confirm_password) {
    $message = "Passwords do not match.";
}

This tiny addition saves users from frustration and makes the registration process smoother.

Step 13: Redirect logged-in users away from login and register pages

A good login system should also behave intelligently. If someone is already logged in, there is no reason to show the login page again. You can redirect them to the dashboard.

At the top of login.php:

session_start();

if (isset($_SESSION["user_id"])) {
    header("Location: dashboard.php");
    exit;
}

Do the same in register.php if you want to stop logged-in users from creating another account unless that is part of your design.

This kind of refinement makes the user experience feel more natural and prevents unnecessary steps.

Step 14: Understand how the full flow works

At this point, the full authentication flow looks like this:

A new user visits the registration page and enters their details.
The system checks the input, hashes the password, and saves the user in MySQL.
The user goes to the login page and enters their email and password.
PHP retrieves the stored user record and verifies the password.
If the credentials are correct, session variables are created.
The user is redirected to a protected dashboard.
When the user clicks logout, the session is destroyed and access ends.

That flow is the heart of many modern applications. Once you understand it well, you can adapt it to admin panels, member areas, online stores, booking systems, and more.

Step 15: A more complete login and registration example

To help you see how the pieces fit together, here is a simplified but practical registration handler:

<?php
require_once "../config/db.php";
session_start();

$message = "";

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $name = trim($_POST["name"]);
    $email = trim($_POST["email"]);
    $password = $_POST["password"];
    $confirm_password = $_POST["confirm_password"];

    if (empty($name) || empty($email) || empty($password) || empty($confirm_password)) {
        $message = "Please fill in all fields.";
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $message = "Invalid email address.";
    } elseif ($password !== $confirm_password) {
        $message = "Passwords do not match.";
    } elseif (strlen($password) < 8) {
        $message = "Password must be at least 8 characters.";
    } else {
        $check = $pdo->prepare("SELECT id FROM users WHERE email = ?");
        $check->execute([$email]);

        if ($check->fetch()) {
            $message = "Email already registered.";
        } else {
            $hashedPassword = password_hash($password, PASSWORD_DEFAULT);
            $stmt = $pdo->prepare("INSERT INTO users (name, email, password) VALUES (?, ?, ?)");
            $stmt->execute([$name, $email, $hashedPassword]);

            $message = "Account created successfully.";
        }
    }
}
?>

And here is a cleaner login handler:

<?php
require_once "../config/db.php";
session_start();

$message = "";

if ($_SERVER["REQUEST_METHOD"] === "POST") {
    $email = trim($_POST["email"]);
    $password = $_POST["password"];

    if (empty($email) || empty($password)) {
        $message = "Please enter both email and password.";
    } else {
        $stmt = $pdo->prepare("SELECT id, name, email, password FROM users WHERE email = ?");
        $stmt->execute([$email]);
        $user = $stmt->fetch(PDO::FETCH_ASSOC);

        if ($user && password_verify($password, $user["password"])) {
            $_SESSION["user_id"] = $user["id"];
            $_SESSION["user_name"] = $user["name"];
            $_SESSION["user_email"] = $user["email"];

            header("Location: dashboard.php");
            exit;
        } else {
            $message = "Incorrect email or password.";
        }
    }
}
?>

These examples are intentionally straightforward so you can understand each step. Once the logic is clear, you can enhance it further with remember-me features, email verification, password reset forms, and user roles.

Step 16: Useful enhancements for a real project

A basic login system is only the beginning. In a real application, you may want to add features such as:

Email verification to confirm the account belongs to the user
Password reset via email
Remember me functionality using secure tokens
Role-based access control for admins and users
Profile pages for updating user information
CSRF protection on forms
Rate limiting or login throttling to reduce brute-force attempts

These features take the system from simple to professional. Even if you do not add them immediately, it helps to design your project with them in mind.

Step 17: Why CSRF protection matters

CSRF, or Cross-Site Request Forgery, is a type of attack where a malicious site tricks a logged-in user’s browser into making an unwanted request. For login and registration forms, CSRF protection is a smart addition.

A common pattern is to generate a token in the session and include it in the form:

$_SESSION["csrf_token"] = bin2hex(random_bytes(32));

Then include it in the form:

<input type="hidden" name="csrf_token" value="<?php echo $_SESSION["csrf_token"]; ?>">

And validate it after form submission:

if (!hash_equals($_SESSION["csrf_token"], $_POST["csrf_token"])) {
    die("Invalid CSRF token.");
}

This adds another layer of safety and is worth implementing in production projects.

Step 18: A human approach to writing authentication code

When people first learn login systems, they often focus only on the technical part: the SQL query, the session variable, the redirect. But authentication is really about trust. A user is giving your site their identity, even if it is only an email and password. That means the system should feel careful, respectful, and predictable.

A good login form does not surprise the user. It tells them clearly what went wrong. A good registration system does not accept sloppy data. A good logout button ends the session cleanly. These small details create a sense of reliability. That is why careful code is not just a technical concern, it is also a user experience concern.

The best systems are often the ones that feel simple because the complexity is handled quietly in the background. When a user logs in, they do not want to think about hashing algorithms or query safety. They just want the process to work, securely and smoothly. Your job as a developer is to make that happen.

Step 19: Final checklist

Before using your login system in a project, make sure you have covered the following:

Your database stores hashed passwords, not plain text
Your login form validates required fields
Your queries use prepared statements
Your dashboard checks if the session exists
Your logout destroys the session
Your output is escaped with htmlspecialchars()
Your registration page prevents duplicate emails
Your forms display clear feedback messages

If these points are in place, your system is already much stronger than many beginner projects.

Conclusion

Building a login system using MySQL and PHP is one of the most useful projects you can complete as a web developer. It teaches database design, form handling, password security, sessions, redirects, and safe coding practices in a way that feels practical and meaningful. Once you finish it, you will not only have a working authentication system, but also a deeper understanding of how real web applications manage users.

The most important lesson is that a login system should never be built casually. Security matters from the first line of code. Use hashed passwords, prepared statements, session checks, and proper validation. Keep the user experience clean and the logic organized. When you do that, you create a system that is both functional and trustworthy.

This is the kind of project that stays useful long after you finish it. You can reuse it in future websites, expand it with advanced features, or adapt it into a larger application. And more importantly, you will have learned a core skill that every PHP developer needs.

#PHP login system #MySQL login system #PHP authentication #user registration PHP #password hashing PHP #PHP sessions #secure login system #PHP MySQL tutorial #login form PHP

Subscribe to our newsletter

12k+

Subscribers

Weekly

Frequency

Free

Always