Skip to main content

Welcome to a Grasp lesson

Create a free account to keep studying and create your own study path.

Lesson 46

User Login System: Database Verification

Recommended resources

Hello! Welcome to your next lesson on building a complete web application.

In our last lesson, we built a secure registration system. We learned how to validate user input and, most importantly, how to use password_hash() to securely store user credentials in the database. Now, with registered users in our system, it's time to let them log in.

Today, we will implement a user login system by verifying credentials against the database. This is the other half of the authentication coin and a fundamental skill for any application that manages user accounts. We'll cover everything from processing the login form to verifying the password against its stored hash and initiating a user session.

1. The Anatomy of a Login

Before we write any code, let's outline the process. A successful login flow involves a precise sequence of events on the back-end.

  1. Receive Credentials: The user submits their username (or email) and password via an HTML form using the POST method.
  2. Find the User: The PHP script queries the database to find a user record matching the submitted username/email. This must be done using a prepared statement to prevent SQL injection.
  3. Verify Password: If a user is found, the script uses the password_verify() function. This function securely compares the plaintext password submitted by the user against the hashed password stored in the database.
  4. Manage Session: If the password is correct, the script starts a session with session_start() and stores identifying user information (like their ID and username) in the $_SESSION superglobal.
  5. Redirect: The user is then redirected to a protected members-only area, like a dashboard or welcome page.
  6. Handle Failure: If the user is not found or the password is incorrect, a generic error message is displayed on the login form.

The user interface for this process typically involves a login form, a protected page, and perhaps profile information, as shown below.

PHP User Authentication System UI Overview
This image shows the key UI elements in an authentication system: the login form where the process starts, and the resulting user profile page after a successful login.

2. The Core Logic: Finding and Verifying

The heart of the login script is surprisingly concise. It boils down to fetching a user record and checking a password. For this critical piece of security logic, it's best to see a clean, professional example.

Authenticating a user using PDO and password_verify()

The website phpdelusions.net is an excellent resource for modern PHP best practices. Let's look at their article that presents the core authentication logic using PDO in just a few lines of code.

Read the main article content (before the comments). Focus on the PHP code block and the explanation for each line. This snippet is the essential logic we will build upon.

The key lines from the article are:

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

if ($user && password_verify($_POST['pass'], $user['pass'])) {
    // Login successful
} else {
    // Login failed
}

Let's break down that if condition, as it's doing two crucial things:

  1. if ($user ...): When $stmt->fetch() finds no matching user, it returns false. This first part of the condition checks if a user was found at all. If $user is false, the entire condition short-circuits and fails immediately, without even trying to verify the password.
  2. ... && password_verify(...): Only if a user was found does the code proceed to this check. password_verify() is the magic function that securely compares the plaintext password from the form ($_POST['pass']) with the hash from the database ($user['pass']). It knows how to handle the algorithm and salt embedded in the hash created by password_hash().

A common point of confusion is how the database search works. Why don't we need to fetch the email from the database and compare it in PHP? The prepared statement handles this for us.

Authenticating a user using PDO and password_verify()

The comment section of the same article provides an excellent explanation for this common question.

Find the comment by 'Adolf Ezeribe' starting with 'Please I am lost here!' and read the detailed reply from the site author. This will clarify how the database performs the search for you when you execute a prepared SELECT statement.


3. Building the Complete login.php Script

Now, let's expand that core logic into a full, working login.php file. This will include the HTML form, input validation, session handling, and redirection. The following video gives a great step-by-step walkthrough.

Step 3.1: Handling Form Submission and Basic Validation

First, our script needs to check if the form was submitted and if the required fields are filled out.

PHP Login System Tutorial with MYSQL

This video from the FollowAndrew channel provides a practical, procedural implementation of a complete login system. Let's start with how it handles the initial form submission.

Watch from 59:05 to 01:04:28. Notice how the script checks for the submission of the login button and then validates that the email and password fields are not empty, preparing an error message if they are.

Step 3.2: Querying the Database and Verifying Credentials

Next comes the core security logic: finding the user and verifying their password. Pay close attention to the security consideration for error messages, which is very relevant to your interest in CyberSec.

PHP Login System Tutorial with MYSQL

Now, the video implements the database query and password verification we discussed earlier.

Continue watching from 01:04:28 to 01:10:18. Focus on: The PDO prepared SELECT statement to find the user by email. The if ($row) check to see if a user was found. The call to password_verify(). The vague error message 'Wrong email or password'. This is a security best practice to prevent attackers from discovering which emails are registered in your system.

Step 3.3: Managing the Session and Redirecting

If password_verify() returns true, the user is authenticated. The final step is to record this fact in a session and send them to a protected page.

PHP Login System Tutorial with MYSQL

With the user's identity confirmed, we log them in by creating a session and redirecting them to a private area of the site.

Watch from 01:10:18 to 01:17:27. Observe how session_start() must be called, how user data (ID, name, email) is stored in $_SESSION, and how the header() function redirects to welcome.php. Critically, note the advice not to store the password hash in the session.


4. A Complete Example

To bring everything together, here is a consolidated login.php script based on the principles we've covered, using PDO for database interaction. This script would handle its own form submission and display errors.

<?php
// Always start the session at the very top of the script.
session_start();

// If the user is already logged in, redirect them to the welcome page.
if (isset($_SESSION["loggedin"]) && $_SESSION["loggedin"] === true) {
    header("location: welcome.php");
    exit;
}

// Include the database connection file.
require_once "config.php"; // This file should contain your PDO connection object, e.g., $pdo

$email = $password = "";
$email_err = $password_err = $login_err = "";

// Process form data when the form is submitted.
if ($_SERVER["REQUEST_METHOD"] == "POST") {

    // Check if email is empty.
    if (empty(trim($_POST["email"]))) {
        $email_err = "Please enter email.";
    } else {
        $email = trim($_POST["email"]);
    }

    // Check if password is empty.
    if (empty(trim($_POST["password"]))) {
        $password_err = "Please enter your password.";
    } else {
        $password = trim($_POST["password"]);
    }

    // Validate credentials if there are no input errors.
    if (empty($email_err) && empty($password_err)) {
        $sql = "SELECT id, username, password FROM users WHERE email = :email";
        
        if ($stmt = $pdo->prepare($sql)) {
            $stmt->bindParam(":email", $param_email, PDO::PARAM_STR);
            $param_email = $email;
            
            if ($stmt->execute()) {
                // Check if email exists, if yes then verify password.
                if ($stmt->rowCount() == 1) {
                    if ($row = $stmt->fetch()) {
                        $id = $row["id"];
                        $username = $row["username"];
                        $hashed_password = $row["password"];
                        if (password_verify($password, $hashed_password)) {
                            // Password is correct, so start a new session.
                            session_start();
                            
                            // Store data in session variables.
                            $_SESSION["loggedin"] = true;
                            $_SESSION["id"] = $id;
                            $_SESSION["username"] = $username;                            
                            
                            // Redirect user to welcome page.
                            header("location: welcome.php");
                        } else {
                            // Password is not valid, display a generic error message.
                            $login_err = "Invalid email or password.";
                        }
                    }
                } else {
                    // Email doesn't exist, display a generic error message.
                    $login_err = "Invalid email or password.";
                }
            } else {
                echo "Oops! Something went wrong. Please try again later.";
            }

            // Close statement
            unset($stmt);
        }
    }
    
    // Close connection
    unset($pdo);
}
?>

<!DOCTYPE html>
<html>
<head>
    <title>Login</title>
</head>
<body>
    <h2>Login</h2>
    <?php 
    if(!empty($login_err)){
        echo '<div style="color:red;">' . $login_err . '</div>';
    }        
    ?>
    <form action="<?php echo htmlspecialchars($_SERVER["PHP_SELF"]); ?>" method="post">
        <!-- Form fields for email and password -->
    </form>
</body>
</html>

Conclusion

You have now built the core of a user authentication system. By combining the registration script from the last lesson with the login script from this one, you have the foundational components for any web application that requires user accounts.

Key Takeaways:

  • Verification Flow: The login process is a strict sequence: find the user by a unique identifier (like email), then and only then, verify the submitted password against the stored hash.
  • password_verify() is Essential: It is the secure and correct counterpart to password_hash().
  • Sessions are State: A successful login is marked by starting a session and storing user identifiers in the $_SESSION superglobal. This is how your application "remembers" the user across different pages.
  • Security Through Obscurity: Vague error messages like "Invalid email or password" prevent attackers from enumerating valid user accounts on your platform.

Preview of the Next Lesson:
Our authentication system is almost complete. The final pieces are allowing users to log out and protecting pages from unauthorized access. In our next lesson, we will create a logout mechanism to destroy the user's session and learn how to check for a valid session on pages like welcome.php to ensure only logged-in users can see them.