PHP Fundamentals

User Registration System

15 min Lesson 41 of 45

User Registration System

Building a secure user registration system is a fundamental skill in web development. In this lesson, we'll create a complete registration system with proper validation, password hashing, and database integration.

Database Setup

First, let's create a users table to store user information:

SQL:
CREATE TABLE users (
    id INT AUTO_INCREMENT PRIMARY KEY,
    username VARCHAR(50) UNIQUE NOT NULL,
    email VARCHAR(100) UNIQUE NOT NULL,
    password VARCHAR(255) NOT NULL,
    full_name VARCHAR(100) NOT NULL,
    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    last_login TIMESTAMP NULL,
    is_active BOOLEAN DEFAULT TRUE,
    INDEX idx_email (email),
    INDEX idx_username (username)
);
Note: The password field is VARCHAR(255) to accommodate modern hashing algorithms like bcrypt, which produce 60-character hashes. We use 255 for future compatibility.

Registration Form HTML

Create a user-friendly registration form with proper validation attributes:

register.php:
<!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>
        * {
            margin: 0;
            padding: 0;
            box-sizing: border-box;
        }

        body {
            font-family: Arial, sans-serif;
            background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
            min-height: 100vh;
            display: flex;
            align-items: center;
            justify-content: center;
            padding: 20px;
        }

        .register-container {
            background: white;
            padding: 40px;
            border-radius: 10px;
            box-shadow: 0 10px 40px rgba(0,0,0,0.2);
            max-width: 500px;
            width: 100%;
        }

        h1 {
            color: #333;
            margin-bottom: 30px;
            text-align: center;
        }

        .form-group {
            margin-bottom: 20px;
        }

        label {
            display: block;
            margin-bottom: 5px;
            color: #555;
            font-weight: bold;
        }

        input {
            width: 100%;
            padding: 12px;
            border: 2px solid #ddd;
            border-radius: 5px;
            font-size: 16px;
            transition: border-color 0.3s;
        }

        input:focus {
            outline: none;
            border-color: #667eea;
        }

        .error {
            color: #e74c3c;
            font-size: 14px;
            margin-top: 5px;
            display: block;
        }

        .success {
            background: #2ecc71;
            color: white;
            padding: 15px;
            border-radius: 5px;
            margin-bottom: 20px;
            text-align: center;
        }

        button {
            width: 100%;
            padding: 12px;
            background: #667eea;
            color: white;
            border: none;
            border-radius: 5px;
            font-size: 16px;
            font-weight: bold;
            cursor: pointer;
            transition: background 0.3s;
        }

        button:hover {
            background: #5568d3;
        }

        .login-link {
            text-align: center;
            margin-top: 20px;
            color: #555;
        }

        .login-link a {
            color: #667eea;
            text-decoration: none;
            font-weight: bold;
        }

        .password-strength {
            height: 5px;
            margin-top: 5px;
            border-radius: 3px;
            transition: all 0.3s;
        }

        .strength-weak { background: #e74c3c; width: 33%; }
        .strength-medium { background: #f39c12; width: 66%; }
        .strength-strong { background: #2ecc71; width: 100%; }
    </style>
</head>
<body>
    <div class="register-container">
        <h1>Create Account</h1>

        <?php if (isset($success_message)): ?>
            <div class="success"><?php echo $success_message; ?></div>
        <?php endif; ?>

        <form method="POST" action="register.php" id="registerForm">
            <div class="form-group">
                <label for="full_name">Full Name</label>
                <input type="text" id="full_name" name="full_name"
                       value="<?php echo htmlspecialchars($full_name ?? ''); ?>"
                       required>
                <?php if (isset($errors['full_name'])): ?>
                    <span class="error"><?php echo $errors['full_name']; ?></span>
                <?php endif; ?>
            </div>

            <div class="form-group">
                <label for="username">Username</label>
                <input type="text" id="username" name="username"
                       value="<?php echo htmlspecialchars($username ?? ''); ?>"
                       required>
                <?php if (isset($errors['username'])): ?>
                    <span class="error"><?php echo $errors['username']; ?></span>
                <?php endif; ?>
            </div>

            <div class="form-group">
                <label for="email">Email Address</label>
                <input type="email" id="email" name="email"
                       value="<?php echo htmlspecialchars($email ?? ''); ?>"
                       required>
                <?php if (isset($errors['email'])): ?>
                    <span class="error"><?php echo $errors['email']; ?></span>
                <?php endif; ?>
            </div>

            <div class="form-group">
                <label for="password">Password</label>
                <input type="password" id="password" name="password" required>
                <div class="password-strength" id="strengthBar"></div>
                <?php if (isset($errors['password'])): ?>
                    <span class="error"><?php echo $errors['password']; ?></span>
                <?php endif; ?>
            </div>

            <div class="form-group">
                <label for="confirm_password">Confirm Password</label>
                <input type="password" id="confirm_password" name="confirm_password" required>
                <?php if (isset($errors['confirm_password'])): ?>
                    <span class="error"><?php echo $errors['confirm_password']; ?></span>
                <?php endif; ?>
            </div>

            <button type="submit">Register</button>
        </form>

        <div class="login-link">
            Already have an account? <a href="login.php">Login here</a>
        </div>
    </div>

    <script>
        // Password strength indicator
        document.getElementById('password').addEventListener('input', function(e) {
            const password = e.target.value;
            const strengthBar = document.getElementById('strengthBar');

            let strength = 0;
            if (password.length >= 8) strength++;
            if (/[a-z]/.test(password) && /[A-Z]/.test(password)) strength++;
            if (/[0-9]/.test(password)) strength++;
            if (/[^a-zA-Z0-9]/.test(password)) strength++;

            strengthBar.className = 'password-strength';
            if (strength <= 1) strengthBar.classList.add('strength-weak');
            else if (strength <= 3) strengthBar.classList.add('strength-medium');
            else strengthBar.classList.add('strength-strong');
        });
    </script>
</body>
</html>

Registration Logic with Validation

Now let's add the PHP logic to handle form submission securely:

register.php (PHP logic at the top):
<?php
session_start();

// Database configuration
$host = 'localhost';
$dbname = 'your_database';
$db_username = 'your_username';
$db_password = 'your_password';

// Initialize variables
$errors = [];
$success_message = '';
$full_name = '';
$username = '';
$email = '';

// Create database connection
try {
    $pdo = new PDO("mysql:host=$host;dbname=$dbname;charset=utf8mb4", $db_username, $db_password);
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    die("Connection failed: " . $e->getMessage());
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    // Get and sanitize input
    $full_name = trim($_POST['full_name'] ?? '');
    $username = trim($_POST['username'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $password = $_POST['password'] ?? '';
    $confirm_password = $_POST['confirm_password'] ?? '';

    // Validate full name
    if (empty($full_name)) {
        $errors['full_name'] = 'Full name is required';
    } elseif (strlen($full_name) < 2) {
        $errors['full_name'] = 'Full name must be at least 2 characters';
    } elseif (strlen($full_name) > 100) {
        $errors['full_name'] = 'Full name must not exceed 100 characters';
    }

    // Validate username
    if (empty($username)) {
        $errors['username'] = 'Username is required';
    } elseif (strlen($username) < 3) {
        $errors['username'] = 'Username must be at least 3 characters';
    } elseif (strlen($username) > 50) {
        $errors['username'] = 'Username must not exceed 50 characters';
    } elseif (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
        $errors['username'] = 'Username can only contain letters, numbers, and underscores';
    } else {
        // Check if username already exists
        $stmt = $pdo->prepare('SELECT id FROM users WHERE username = ?');
        $stmt->execute([$username]);
        if ($stmt->fetch()) {
            $errors['username'] = 'Username already taken';
        }
    }

    // Validate email
    if (empty($email)) {
        $errors['email'] = 'Email is required';
    } elseif (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors['email'] = 'Invalid email format';
    } else {
        // Check if email already exists
        $stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
        $stmt->execute([$email]);
        if ($stmt->fetch()) {
            $errors['email'] = 'Email already registered';
        }
    }

    // Validate password
    if (empty($password)) {
        $errors['password'] = 'Password is required';
    } elseif (strlen($password) < 8) {
        $errors['password'] = 'Password must be at least 8 characters';
    } elseif (!preg_match('/[A-Z]/', $password)) {
        $errors['password'] = 'Password must contain at least one uppercase letter';
    } elseif (!preg_match('/[a-z]/', $password)) {
        $errors['password'] = 'Password must contain at least one lowercase letter';
    } elseif (!preg_match('/[0-9]/', $password)) {
        $errors['password'] = 'Password must contain at least one number';
    }

    // Validate password confirmation
    if (empty($confirm_password)) {
        $errors['confirm_password'] = 'Please confirm your password';
    } elseif ($password !== $confirm_password) {
        $errors['confirm_password'] = 'Passwords do not match';
    }

    // If no errors, create the user
    if (empty($errors)) {
        try {
            // Hash the password
            $hashed_password = password_hash($password, PASSWORD_DEFAULT);

            // Insert user into database
            $stmt = $pdo->prepare('
                INSERT INTO users (username, email, password, full_name)
                VALUES (?, ?, ?, ?)
            ');
            $stmt->execute([$username, $email, $hashed_password, $full_name]);

            $success_message = 'Registration successful! You can now login.';

            // Clear form fields
            $full_name = '';
            $username = '';
            $email = '';

            // Optionally redirect to login page after 2 seconds
            // header('Refresh: 2; URL=login.php');

        } catch (PDOException $e) {
            $errors['general'] = 'Registration failed. Please try again.';
            // Log the error for debugging
            error_log($e->getMessage());
        }
    }
}
?>
Security Warning: Never store passwords in plain text! Always use password_hash() with PASSWORD_DEFAULT, which currently uses bcrypt and will automatically upgrade to stronger algorithms in future PHP versions.

Helper Functions for Validation

Create reusable validation functions for cleaner code:

validation_helpers.php:
<?php
/**
 * Validate username format and availability
 */
function validateUsername($username, $pdo) {
    if (empty($username)) {
        return 'Username is required';
    }

    if (strlen($username) < 3 || strlen($username) > 50) {
        return 'Username must be between 3 and 50 characters';
    }

    if (!preg_match('/^[a-zA-Z0-9_]+$/', $username)) {
        return 'Username can only contain letters, numbers, and underscores';
    }

    // Check availability
    $stmt = $pdo->prepare('SELECT id FROM users WHERE username = ?');
    $stmt->execute([$username]);
    if ($stmt->fetch()) {
        return 'Username already taken';
    }

    return null; // No error
}

/**
 * Validate email format and availability
 */
function validateEmail($email, $pdo) {
    if (empty($email)) {
        return 'Email is required';
    }

    if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        return 'Invalid email format';
    }

    // Check availability
    $stmt = $pdo->prepare('SELECT id FROM users WHERE email = ?');
    $stmt->execute([$email]);
    if ($stmt->fetch()) {
        return 'Email already registered';
    }

    return null;
}

/**
 * Validate password strength
 */
function validatePassword($password) {
    if (empty($password)) {
        return 'Password is required';
    }

    if (strlen($password) < 8) {
        return 'Password must be at least 8 characters';
    }

    if (!preg_match('/[A-Z]/', $password)) {
        return 'Password must contain at least one uppercase letter';
    }

    if (!preg_match('/[a-z]/', $password)) {
        return 'Password must contain at least one lowercase letter';
    }

    if (!preg_match('/[0-9]/', $password)) {
        return 'Password must contain at least one number';
    }

    // Optional: check for special characters
    // if (!preg_match('/[^a-zA-Z0-9]/', $password)) {
    //     return 'Password must contain at least one special character';
    // }

    return null;
}

/**
 * Sanitize user input
 */
function sanitizeInput($data) {
    $data = trim($data);
    $data = stripslashes($data);
    $data = htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
    return $data;
}
?>

AJAX Registration (Optional Enhancement)

For a better user experience, implement AJAX registration:

register_ajax.php:
<?php
session_start();
header('Content-Type: application/json');

// Include validation helpers
require_once 'validation_helpers.php';

// Database connection
try {
    $pdo = new PDO("mysql:host=localhost;dbname=your_database;charset=utf8mb4",
                   "username", "password");
    $pdo->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
} catch (PDOException $e) {
    echo json_encode(['success' => false, 'message' => 'Database error']);
    exit;
}

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $errors = [];

    $full_name = trim($_POST['full_name'] ?? '');
    $username = trim($_POST['username'] ?? '');
    $email = trim($_POST['email'] ?? '');
    $password = $_POST['password'] ?? '';
    $confirm_password = $_POST['confirm_password'] ?? '';

    // Validate all fields
    if (empty($full_name) || strlen($full_name) < 2) {
        $errors['full_name'] = 'Full name must be at least 2 characters';
    }

    $username_error = validateUsername($username, $pdo);
    if ($username_error) {
        $errors['username'] = $username_error;
    }

    $email_error = validateEmail($email, $pdo);
    if ($email_error) {
        $errors['email'] = $email_error;
    }

    $password_error = validatePassword($password);
    if ($password_error) {
        $errors['password'] = $password_error;
    }

    if ($password !== $confirm_password) {
        $errors['confirm_password'] = 'Passwords do not match';
    }

    if (!empty($errors)) {
        echo json_encode(['success' => false, 'errors' => $errors]);
        exit;
    }

    // Create user
    try {
        $hashed_password = password_hash($password, PASSWORD_DEFAULT);

        $stmt = $pdo->prepare('
            INSERT INTO users (username, email, password, full_name)
            VALUES (?, ?, ?, ?)
        ');
        $stmt->execute([$username, $email, $hashed_password, $full_name]);

        echo json_encode([
            'success' => true,
            'message' => 'Registration successful!',
            'redirect' => 'login.php'
        ]);

    } catch (PDOException $e) {
        echo json_encode(['success' => false, 'message' => 'Registration failed']);
        error_log($e->getMessage());
    }
}
?>
Pro Tip: Use AJAX for real-time validation. Check username/email availability as the user types, providing instant feedback without page reloads.
Exercise:
  1. Create the users table in your database
  2. Implement the registration form with all validation rules
  3. Test with various inputs: short passwords, invalid emails, duplicate usernames
  4. Add a "terms and conditions" checkbox that must be checked before registration
  5. Implement email verification (send a verification code to the user's email)
  6. Add CAPTCHA protection to prevent bot registrations

Best Practices Summary

  • Always hash passwords using password_hash() with PASSWORD_DEFAULT
  • Validate both client-side and server-side - never trust client-side validation alone
  • Use prepared statements to prevent SQL injection
  • Sanitize and validate all inputs before processing
  • Check for duplicate usernames/emails before insertion
  • Use HTTPS to encrypt data transmission
  • Implement rate limiting to prevent brute force attacks
  • Log registration attempts for security monitoring