Build A PHP Login & Registration System From Scratch

by Faj Lennon 53 views

Hey guys, let's dive into building a fully functional PHP login and registration system from scratch! This isn't just about slapping some code together; we're going to create a robust, secure, and easy-to-understand system that you can use as a foundation for your web projects. Forget those complex frameworks for a second; sometimes, understanding the core mechanics is super valuable. We'll cover everything from setting up your database to handling user input securely and managing sessions like a pro. Whether you're a beginner looking to grasp the fundamentals or an intermediate developer wanting a solid reference, this guide is for you. We'll break down each step, explain the 'why' behind the 'what,' and make sure you come away with a working system and a clearer picture of how user authentication really works. Ready to get your hands dirty with some awesome PHP?

Setting Up Your Database: The Foundation of Your System

Alright, the very first thing we need for our PHP login and registration system is a place to store our user data. This means setting up a database. For most of you, MySQL (or its open-source cousin, MariaDB) is going to be your go-to. It's powerful, widely used, and integrates seamlessly with PHP. We'll need a table to hold our user information. Let's call it users. What fields should this users table have? Well, at a minimum, you'll want:

  • id: This will be our primary key, an auto-incrementing unique identifier for each user. Super important for referencing users later.
  • username: The unique name a user chooses to log in with. We'll enforce uniqueness here.
  • email: A user's email address. Also good to enforce uniqueness and validate its format.
  • password: This is where the magic (and security) happens. We will not store passwords in plain text, guys. We'll hash them using PHP's password_hash() function, which is the modern, secure way to do it. This field needs to be long enough to store the hash, typically VARCHAR(255).
  • created_at: A timestamp to record when the user registered. Useful for tracking and auditing.

To create this table, you can use a tool like phpMyAdmin or command-line SQL. Here's a basic SQL query to get you started:

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

Don't forget to set up your database connection in PHP. You'll need your database host (usually localhost), username, password, and the database name. We'll create a separate configuration file (like config.php) for these credentials to keep them organized and out of your main script files. This is a crucial step for security – never hardcode credentials directly into your application logic.

<?php
// config.php
define('DB_HOST', 'localhost');
define('DB_USERNAME', 'your_db_username');
define('DB_PASSWORD', 'your_db_password');
define('DB_NAME', 'your_db_name');

try {
    $pdo = new PDO('mysql:host=' . DB_HOST . ';dbname=' . DB_NAME, DB_USERNAME, DB_PASSWORD);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $pdo->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch (PDOException $e) {
    die('Connection failed: ' . $e->getMessage());
}
?>

Make sure you replace 'your_db_username', 'your_db_password', and 'your_db_name' with your actual database credentials. Using PDO (PHP Data Objects) is highly recommended because it provides a database-agnostic interface and helps prevent SQL injection attacks when used correctly with prepared statements. This setup might seem basic, but it's the bedrock of your entire user management system. A well-structured database and a secure connection are non-negotiable, guys!

Building the Registration Form and Handling Input

Now that our database is ready, let's tackle the user registration process. This involves two main parts: the HTML form where users will enter their details and the PHP script that will process this information and insert it into our database. First, let's craft a clean and user-friendly registration form. We'll use HTML5 for basic validation and accessibility. Remember, the goal is to make it easy for users to sign up while collecting the necessary information.

Here’s a sample register.html (or you can make it a .php file if you want to add dynamic elements later):

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>User Registration</title>
    <style>
        body { font-family: sans-serif; margin: 20px; }
        .form-group { margin-bottom: 15px; }
        label { display: block; margin-bottom: 5px; }
        input[type="text"], input[type="email"], input[type="password"] {
            width: 100%;
            padding: 8px;
            box-sizing: border-box;
        }
        button { padding: 10px 15px; cursor: pointer; }
        .error { color: red; margin-top: 10px; }
    </style>
</head>
<body>
    <h2>Register New Account</h2>
    <form action="register_process.php" method="POST">
        <div class="form-group">
            <label for="username">Username:</label>
            <input type="text" id="username" name="username" required>
        </div>
        <div class="form-group">
            <label for="email">Email:</label>
            <input type="email" id="email" name="email" required>
        </div>
        <div class="form-group">
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required>
        </div>
        <div class="form-group">
            <label for="confirm_password">Confirm Password:</label>
            <input type="password" id="confirm_password" name="confirm_password" required>
        </div>
        <button type="submit">Register</button>
    </form>
    <p class="error"><?php echo isset($error) ? $error : ''; ?></p>
    <p>Already have an account? <a href="login.php">Login here</a></p>
</body>
</html>

Now, for the register_process.php script. This is where the heavy lifting happens. We need to receive the data from the form, validate it thoroughly, hash the password, and then insert it into the database. Security is paramount here, guys! We must prevent common vulnerabilities like SQL injection and cross-site scripting (XSS).

First, include your database configuration file (config.php which we defined earlier).

<?php
// register_process.php
require_once 'config.php'; // Include your database connection file

$error = ''; // Initialize an error variable

// Check if the form was submitted
if ($_SERVER["REQUEST_METHOD"] == "POST") {

    // Retrieve and sanitize input data
    $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING);
    $email = filter_input(INPUT_POST, 'email', FILTER_SANITIZE_EMAIL);
    $password = $_POST['password']; // Will hash this later
    $confirm_password = $_POST['confirm_password'];

    // --- Basic Input Validation ---
    if (empty($username) || empty($email) || empty($password) || empty($confirm_password)) {
        $error = 'All fields are required.';
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $error = 'Invalid email format.';
    } elseif (strlen($password) < 6) {
        $error = 'Password must be at least 6 characters long.';
    } elseif ($password !== $confirm_password) {
        $error = 'Passwords do not match.';
    } else {
        // Check if username or email already exists
        try {
            // Check username
            $stmt = $pdo->prepare('SELECT id FROM users WHERE username = :username');
            $stmt->bindParam(':username', $username);
            $stmt->execute();
            if ($stmt->rowCount() > 0) {
                $error = 'Username is already taken.';
            }

            // Check email
            if (empty($error)) { // Only check email if username is unique
                $stmt = $pdo->prepare('SELECT id FROM users WHERE email = :email');
                $stmt->bindParam(':email', $email);
                $stmt->execute();
                if ($stmt->rowCount() > 0) {
                    $error = 'Email is already registered.';
                }
            }

            // If no errors, proceed to insert
            if (empty($error)) {
                // Hash the password before storing
                $hashed_password = password_hash($password, PASSWORD_DEFAULT);

                $stmt = $pdo->prepare('INSERT INTO users (username, email, password) VALUES (:username, :email, :password)');
                $stmt->bindParam(':username', $username);
                $stmt->bindParam(':email', $email);
                $stmt->bindParam(':password', $hashed_password);

                if ($stmt->execute()) {
                    // Registration successful! Redirect to login page or success page.
                    header('Location: register_success.php'); // Create this page
                    exit();
                } else {
                    $error = 'An unexpected error occurred during registration. Please try again.';
                }
            }

        } catch (PDOException $e) {
            // Log the error for debugging purposes, but show a generic message to the user
            error_log('Registration Error: ' . $e->getMessage());
            $error = 'A database error occurred. Please try again later.';
        }
    }
}

// If there were errors, display them (usually by re-rendering the form or showing a message)
// For simplicity here, we'll just include the registration form and pass the error message
$pageTitle = "User Registration";
include 'register.html'; // Or your registration template file

// In a real application, you would have a separate template file for register.html and potentially
// pass the $error variable to it using a template engine or just by echoing it in the PHP file.
// For this example, we assume register.html can display the $error if it's set.
?>

Key points to remember here:

  1. filter_input(): We use this to sanitize input. FILTER_SANITIZE_STRING removes tags, and FILTER_SANITIZE_EMAIL cleans up the email format. While helpful, it's not a complete security solution on its own.
  2. password_hash(): This is crucial! Never store plain text passwords. PASSWORD_DEFAULT uses the strongest currently available hashing algorithm.
  3. Prepared Statements ($pdo->prepare(), $stmt->bindParam(), $stmt->execute()): This is your main defense against SQL injection. It separates the SQL code from the user-supplied data.
  4. Error Handling: We check for empty fields, invalid email formats, password mismatches, and existing usernames/emails before attempting to insert into the database.
  5. Success Redirect: Upon successful registration, we redirect the user to a confirmation page (register_success.php) using header('Location: ...'). This prevents accidental resubmission if the user refreshes the page.

This registration script is the first major piece of our PHP login and registration system. It's robust, handles basic validation, and prioritizes security. Remember to create the register_success.php file, which could simply say "Registration successful! Please log in."

Creating the Login Form and Session Management

Now for the other half of the equation: the login functionality. This is what allows registered users to access your application. Similar to registration, we'll need an HTML form and a PHP script to process the login attempt. We'll also introduce session management, which is how we'll keep users logged in across multiple pages.

First, let's create the login form. It will be very similar to the registration form but will only ask for a username (or email) and password.

Here’s a sample login.php:

<?php
// login.php
// This page will handle displaying the login form and processing login attempts.
// Session start is crucial for login systems.
session_start();

$error = ''; // Initialize an error variable

// If user is already logged in, redirect them away from the login page
if (isset($_SESSION['user_id'])) {
    header('Location: dashboard.php'); // Assuming you have a dashboard page
    exit();
}

// Check if the form was submitted
if ($_SERVER["REQUEST_METHOD"] == "POST") {

    require_once 'config.php'; // Include your database connection file

    // Retrieve and sanitize input data
    $identifier = filter_input(INPUT_POST, 'identifier', FILTER_SANITIZE_STRING); // Can be username or email
    $password = $_POST['password'];

    if (empty($identifier) || empty($password)) {
        $error = 'Username/Email and password are required.';
    } else {
        try {
            // Prepare a statement to find the user by username OR email
            $stmt = $pdo->prepare('SELECT id, username, password FROM users WHERE username = :identifier OR email = :identifier');
            $stmt->bindParam(':identifier', $identifier);
            $stmt->execute();

            $user = $stmt->fetch(PDO::FETCH_ASSOC);

            // Check if user exists and if the password matches
            if ($user && password_verify($password, $user['password'])) {
                // Password is correct! Start a session.
                session_regenerate_id(true); // For security: prevents session fixation

                $_SESSION['user_id'] = $user['id'];
                $_SESSION['username'] = $user['username']; // Store username for convenience
                // You could store other non-sensitive data here too

                // Redirect to the dashboard or a welcome page
                header('Location: dashboard.php');
                exit();
            } else {
                // Invalid credentials
                $error = 'Invalid username/email or password. Please try again.';
            }

        } catch (PDOException $e) {
            error_log('Login Error: ' . $e->getMessage());
            $error = 'A database error occurred. Please try again later.';
        }
    }
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>User Login</title>
    <style>
        body { font-family: sans-serif; margin: 20px; }
        .form-group { margin-bottom: 15px; }
        label { display: block; margin-bottom: 5px; }
        input[type="text"], input[type="email"], input[type="password"] {
            width: 100%;
            padding: 8px;
            box-sizing: border-box;
        }
        button { padding: 10px 15px; cursor: pointer; }
        .error { color: red; margin-top: 10px; }
    </style>
</head>
<body>
    <h2>Login to Your Account</h2>
    <form action="login.php" method="POST">
        <div class="form-group">
            <label for="identifier">Username or Email:</label>
            <input type="text" id="identifier" name="identifier" required>
        </div>
        <div class="form-group">
            <label for="password">Password:</label>
            <input type="password" id="password" name="password" required>
        </div>
        <button type="submit">Login</button>
    </form>
    <p class="error"><?php echo isset($error) ? $error : ''; ?></p>
    <p>Don't have an account? <a href="register.php">Register here</a></p>
</body>
</html>

Now, let's break down the login.php script:

  1. session_start(): This is the most critical line for session management. It must be called before any output is sent to the browser. It either starts a new session or resumes an existing one, allowing us to store and retrieve session variables.
  2. Check Existing Session: We immediately check if $_SESSION['user_id'] is already set. If it is, the user is already logged in, and we redirect them to a protected page (like dashboard.php) to prevent them from accessing the login form again.
  3. Form Submission Handling: When the form is submitted ($_SERVER["REQUEST_METHOD"] == "POST"), we retrieve the submitted username/email (identifier) and password.
  4. Database Query: We prepare a SQL query to select the user's id, username, and password from the users table. The WHERE clause uses an OR condition to allow login using either the username or the email address. This is a common and user-friendly feature.
  5. password_verify(): This function is the counterpart to password_hash(). It takes the plain-text password submitted by the user and the stored hashed password and checks if they match. It returns true if they match, false otherwise. This is a secure way to compare passwords.
  6. Session Creation: If password_verify() returns true, it means the user is authenticated. We then:
    • session_regenerate_id(true): This is a crucial security measure to prevent session fixation attacks. It invalidates the user's current session ID and creates a new one. Always do this after a successful login.
    • $_SESSION['user_id'] = $user['id'];: We store the user's unique ID in the session. This is the primary way we'll know if a user is logged in on subsequent requests.
    • $_SESSION['username'] = $user['username'];: We also store the username for convenience, so we don't have to query the database again every time we want to display the logged-in user's name.
    • Redirect: Finally, we redirect the user to their dashboard (dashboard.php).
  7. Error Handling: If the user doesn't exist, or the password doesn't match, we set an appropriate error message to be displayed on the login form.

Protecting Pages: The Logout Functionality

To make our PHP login and registration system complete, we need a way for users to log out and a way to protect pages so only logged-in users can access them. Let's start with logout.

Create a logout.php file:

<?php
// logout.php

// Start the session
session_start();

// Unset all session variables
$_SESSION = array();

// If you want to destroy the session completely, also delete the session cookie.
// Note: This will destroy the session, and not just the session data!
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(session_name(), '', time() - 42000,
        $params["path"], $params["domain"],
        $params["secure"],
        $params["httponly"]
    );
}

// Finally, destroy the session.
session_destroy();

// Redirect to the login page
header('Location: login.php');
exit();
?>

This logout.php script is straightforward. It starts the session, then effectively destroys it by unsetting all session variables and the session cookie, and finally redirects the user back to the login page. It's good practice to include the cookie deletion part to ensure the session is truly terminated.

Now, protecting pages is done by checking the session at the beginning of any PHP file that requires authentication. Let's create a simple dashboard.php example:

<?php
// dashboard.php

// Start the session
session_start();

// Check if the user is logged in
if (!isset($_SESSION['user_id'])) {
    // If not logged in, redirect to the login page
    header('Location: login.php');
    exit(); // IMPORTANT: Stop script execution after redirection
}

// If we reach here, the user is logged in
$username = $_SESSION['username']; // Get the username from the session
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>User Dashboard</title>
    <style>
        body { font-family: sans-serif; margin: 20px; }
        a { margin-right: 10px; }
    </style>
</head>
<body>
    <h1>Welcome, <?php echo htmlspecialchars($username); ?>!</h1>
    <p>This is your secure dashboard.</p>
    <nav>
        <a href="#">Profile</a>
        <a href="#">Settings</a>
        <a href="logout.php">Logout</a>
    </nav>
</body>
</html>

In dashboard.php, the first thing we do is session_start() and then check if $_SESSION['user_id'] is set. If it's not set, we redirect the user to login.php. If it is set, the script continues, and we can safely display content intended only for logged-in users. We use htmlspecialchars($username) to prevent XSS attacks when displaying dynamic user content.

This combination of session management, secure login verification, and page protection is what makes your PHP login and registration system truly work. You've now got the core components for a secure user authentication flow. Remember to keep your database credentials safe in config.php and never expose them!

Enhancements and Best Practices

So, guys, we've built a solid PHP login and registration system from scratch. But like any good project, there's always room for improvement and making it even more secure and user-friendly. Let's talk about some key enhancements and best practices you should consider:

  1. Email Verification: For a registration system, it's a huge security and spam-prevention step to require users to verify their email address. After registration, you'd generate a unique token, store it in the database (associated with the user), and send an email to the user with a link containing this token. When they click the link, a PHP script verifies the token and activates their account. This ensures the email address is valid and belongs to the user.

  2. Password Reset Functionality: Users forget passwords! Implementing a secure password reset feature is essential. This typically involves:

    • A form where users enter their email address.
    • Generating a unique, time-limited token and storing it in the database linked to the user.
    • Sending an email with a reset link containing the token.
    • A page to enter a new password, which verifies the token before updating the user's hashed password in the database.
    • Crucially, never reveal the user's password to anyone, not even the admin.
  3. Rate Limiting and Brute-Force Protection: Your login and registration forms are prime targets for automated attacks. Implement measures like:

    • CAPTCHA: Add CAPTCHA to registration and possibly login forms after a few failed attempts.
    • IP Address Blocking: Temporarily block IP addresses that make too many failed login attempts in a short period.
    • Login Throttling: Introduce delays between login attempts for a specific user or from a specific IP.
  4. Input Validation Beyond Basic Sanitization: While filter_input is good, consider more specific validation rules. For example:

    • Username: Should contain only alphanumeric characters and underscores.
    • Password Strength: Enforce minimum length, and consider requiring a mix of uppercase, lowercase, numbers, and symbols. Libraries can help with this.
    • CSRF Protection: Cross-Site Request Forgery is a serious vulnerability. Implement CSRF tokens in your forms. This involves generating a unique, secret token for each user session and embedding it in your forms. The server then verifies this token upon submission to ensure the request originated from your application and not a malicious third party.
  5. Secure Session Handling: We used session_regenerate_id() which is great. Also, consider:

    • Session Timeouts: Automatically log users out after a period of inactivity.
    • httponly and secure flags: Ensure your session cookies are set with these flags in your php.ini or during setcookie() calls for enhanced security.
  6. Error Logging: Instead of just echoing generic error messages, log detailed errors (like database connection issues or unexpected exceptions) to a file on the server. This helps you debug without revealing sensitive information to users.

  7. Code Organization and Templating: As your application grows, keep your code clean. Use functions, classes, and potentially a simple templating engine (like Twig or even just basic PHP includes) to separate logic from presentation. This makes your PHP login and registration system much easier to maintain.

  8. HTTPS: Always use HTTPS for your website. This encrypts the data transmitted between the user's browser and your server, protecting login credentials and other sensitive information from being intercepted.

By incorporating these enhancements, you'll transform your basic system into a professional, secure, and user-friendly authentication solution. Building a robust PHP login and registration system from scratch is a fantastic learning experience that will serve you well in countless web development projects. Keep coding, keep learning, and stay secure!