Login and registration forms in PHP PDO and OOPS


A secure login and registration system is the backbone of any dynamic web application. Whether you are building an e-commerce platform, a SaaS product, or a membership blog, users need a safe way to create accounts and access their dashboards.

In this 2027 Secure Authentication Guide, we will build an advanced Login and Registration form using PHP 8+, PDO (PHP Data Objects), and OOP (Object-Oriented Programming) concepts.

To meet modern security standards, this tutorial strictly implements:

  • PDO Prepared Statements: To prevent SQL Injection.
  • CSRF Tokens: To prevent Cross-Site Request Forgery attacks.
  • Secure Password Hashing: Using modern password_hash() algorithms.
  • Session Security: Using session_regenerate_id(true) to prevent session fixation and hijacking.
  • Strict Backend Validation: Validating emails, string lengths, and duplicate records.

1. Create a MySQL Database Table

First, we need to create a database table to store user information securely. Execute the following SQL query in your phpMyAdmin. Notice we use DATETIME DEFAULT CURRENT_TIMESTAMP to handle dates automatically.

CREATE TABLE `users` (
  `id` INT(11) NOT NULL AUTO_INCREMENT,
  `fname` VARCHAR(100) DEFAULT NULL,
  `lname` VARCHAR(100) DEFAULT NULL,
  `username` VARCHAR(50) NOT NULL,
  `email` VARCHAR(150) NOT NULL,
  `password` VARCHAR(255) NOT NULL,
  `created_date` DATETIME DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`),
  UNIQUE KEY `username` (`username`),
  UNIQUE KEY `email` (`email`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

2. Secure PDO Connection File (config.php)

Let's create the database connection file. We will use PDO because it supports multiple database types and offers robust security features.

config.php

<?php 
define('DBNAME', 'test'); // Your database name
define('DBUSER', 'root'); // Your database username
define('DBPASS', '');     // Your database password
define('DBHOST', 'localhost');

try {
    $dsn = 'mysql:host=' . DBHOST . ';dbname=' . DBNAME . ';charset=utf8mb4';
    $db = new PDO($dsn, DBUSER, DBPASS);
    // Set PDO error mode to exception for better debugging
    $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
    $db->setAttribute(PDO::ATTR_DEFAULT_FETCH_MODE, PDO::FETCH_ASSOC);
} catch(PDOException $e) {
    die("Database Connection Failed: " . $e->getMessage());
}
?>

3. Autoload & Session Security (autoload.php)

The autoload.php file handles two critical tasks: automatically loading our OOP classes and setting up secure sessions with a CSRF Token.

autoload.php

<?php 
// 1. Enforce Secure Sessions
session_set_cookie_params([
    'httponly' => true,
    'samesite' => 'Strict'
]);
session_start(); 

// 2. Generate CSRF Token for Form Security
if (empty($_SESSION['csrf_token'])) {
    $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
}

require_once("config.php");

// 3. Autoload Classes automatically
spl_autoload_register(function($className) {
    require_once("classes/$className.php");
});

// Initialize User Object
$getUser = new User($db);
?>

Note: Create a folder named classes in your project directory. We will save our PHP class files inside this folder.

4. Abstract Basic Class (classes/Basic.php)

Abstraction hides complex implementation details. We create an abstract Basic class to handle data sanitization, which we will extend in our User class.

classes/Basic.php

<?php 
abstract class Basic {
    
    // Sanitize user inputs to prevent XSS and malformed data
    public function sanitize($var, $type) {
        $filter = false;
        switch($type) {
            case 'email':
                $var = substr($var, 0, 254);
                $filter = FILTER_VALIDATE_EMAIL;    
                break;
            case 'int':
                $filter = FILTER_VALIDATE_INT;
                break;
            case 'string':
            default:
                $var = htmlspecialchars(trim($var), ENT_QUOTES, 'UTF-8');
                return $var;
        }
        return trim(filter_var($var, $filter)); 
    }

    public function easy_date($date) {  
        return date('d M Y, h:i A', strtotime($date));  
    }
}
?>

5. Complete User Auth Class (classes/User.php)

This is the core engine of our application. It handles validation, registration, secure login (with session_regenerate_id), and user fetching. Save this entire code inside the classes folder.

classes/User.php

<?php 
class User extends Basic {
    private $dbConnection;

    function __construct($db) {
        $this->dbConnection = $db;
    }

    // --- REGISTRATION VALIDATION ---
    public function validation($fname, $lname, $username, $email, $password, $passwordConfirm) {
        $error = [];
        
        $fname = $this->sanitize($fname, 'string');
        $lname = $this->sanitize($lname, 'string');
        $username = $this->sanitize($username, 'string');
        $email = $this->sanitize($email, 'email');

        if(strlen($fname) < 3 || !preg_match("/^[a-zA-Z\s]+$/", $fname)) {
            $error[] = 'First Name must be at least 3 letters and contain no symbols.';
        }
        
        if(!preg_match("/^[a-z0-9_-]{3,20}$/i", $username)) {
            $error[] = 'Username must be 3-20 characters long and contain no spaces.';
        }
        
        if(!$email) {
            $error[] = 'Please enter a valid email address.';
        }

        // Check for duplicate Username
        $stmt = $this->dbConnection->prepare("SELECT id FROM users WHERE username = :username");
        $stmt->execute([':username' => $username]);
        if($stmt->rowCount() > 0) { $error[] = 'Username already exists.'; }

        // Check for duplicate Email
        $stmt = $this->dbConnection->prepare("SELECT id FROM users WHERE email = :email");
        $stmt->execute([':email' => $email]);
        if($stmt->rowCount() > 0) { $error[] = 'Email already exists.'; }

        if(strlen($password) < 6) {
            $error[] = 'Password must be at least 6 characters long.';
        }
        if($password !== $passwordConfirm) {
            $error[] = 'Passwords do not match.';
        }

        return count($error) > 0 ? $error : null;
    }

    // --- REGISTER USER ---
    public function register($fname, $lname, $username, $email, $password) {
        $fname = $this->sanitize($fname, 'string');
        $lname = $this->sanitize($lname, 'string');
        $username = $this->sanitize($username, 'string');
        
        // Hash password securely (Automatically uses bcrypt/argon2id depending on PHP version)
        $hashedpassword = password_hash($password, PASSWORD_DEFAULT);

        $sql = "INSERT INTO users (fname, lname, username, email, password) 
                VALUES (:fname, :lname, :username, :email, :password)";
        
        $stmt = $this->dbConnection->prepare($sql);
        return $stmt->execute([
            ':fname' => $fname,
            ':lname' => $lname,
            ':username' => $username,
            ':email' => $email,
            ':password' => $hashedpassword
        ]);
    }

    // --- LOGIN USER ---
    public function login($login_var, $password) {
        $login_var = $this->sanitize($login_var, 'string');

        $sql = "SELECT id, password FROM users WHERE username = :login_var OR email = :login_var LIMIT 1"; 
        $stmt = $this->dbConnection->prepare($sql);
        $stmt->execute([':login_var' => $login_var]);
        
        $user = $stmt->fetch();

        // Verify Hash
        if($user && password_verify($password, $user['password'])) {
            // CRITICAL: Prevent Session Fixation attacks
            session_regenerate_id(true); 
            
            $_SESSION["logged_in"] = true; 
            $_SESSION["userid"] = $user['id']; 
            return true; 
        }
        return false; 
    }

    public function is_loggedin() {
        return (isset($_SESSION['logged_in']) && $_SESSION['logged_in'] === true);
    }  

    public function fetch_user($userid) {
        $stmt = $this->dbConnection->prepare("SELECT * FROM users WHERE id = :id");
        $stmt->execute([':id' => $userid]);
        return $stmt->fetch();
    }

    public function logout() {
        session_unset();
        session_destroy();  
    } 
}
?>

6. Secure Registration Form (signup.php)

We use Bootstrap 5 to create a responsive registration UI. Notice the inclusion of the <input type="hidden" name="csrf_token"> field. This ensures that the form submission is genuine and prevents automated bot attacks.

signup.php

<?php 
require_once("autoload.php");

if($getUser->is_loggedin()) {
    header("Location: account.php"); 
    exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Secure Signup - Techno Smarter</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
    <link rel="stylesheet" href="style.css">
</head>
<body>
<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-8">
            <div class="card shadow-sm p-4">
                <h3 class="text-center mb-4">Create an Account</h3>
                
                <?php
                if(isset($_POST['submit_form'])) {
                    // CSRF Token Validation
                    if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
                        die('<div class="alert alert-danger">CSRF token validation failed!</div>');
                    }

                    $fname = $_POST['fname'];
                    $lname = $_POST['lname'];
                    $username = $_POST['username'];
                    $email = $_POST['email'];
                    $password = $_POST['password'];
                    $passwordConfirm = $_POST['passwordConfirm'];
                    
                    $error = $getUser->validation($fname, $lname, $username, $email, $password, $passwordConfirm);
                    
                    if($error === null) {
                        if($getUser->register($fname, $lname, $username, $email, $password)) {
                            header("Location: login.php?register=success"); 
                            exit();
                        } else {
                            $error[] = 'Something went wrong. Please try again.';
                        }
                    }

                    // Display Errors
                    if(isset($error)) { 
                        foreach($error as $err) { 
                            echo '<div class="alert alert-danger">'.$err.'</div>'; 
                        }
                    }
                }
                ?>

                <form action="" method="POST">
                    <!-- CSRF Token Hidden Field -->
                    <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
                    
                    <div class="row"> 
                        <div class="col-sm-6 mb-3">
                            <label>First Name</label>
                            <input type="text" class="form-control" name="fname" required>
                        </div>
                        <div class="col-sm-6 mb-3">
                            <label>Last Name</label>
                            <input type="text" class="form-control" name="lname" required>
                        </div>
                    </div>

                    <div class="row"> 
                        <div class="col-sm-6 mb-3">
                            <label>Username</label>
                            <input type="text" class="form-control" name="username" required>
                        </div>
                        <div class="col-sm-6 mb-3">
                            <label>Email Address</label>
                            <input type="email" class="form-control" name="email" required>
                        </div>
                    </div>

                    <div class="row"> 
                        <div class="col-sm-6 mb-3">
                            <label>Password</label>
                            <input type="password" class="form-control" name="password" required>
                        </div>
                        <div class="col-sm-6 mb-3">
                            <label>Confirm Password</label>
                            <input type="password" class="form-control" name="passwordConfirm" required>
                        </div>
                    </div>

                    <button type="submit" class="btn btn-primary w-100" name="submit_form">Sign Up</button>
                </form>
                
                <p class="text-center mt-3">Already have an account? <a href="login.php">Log In</a></p>
            </div>
        </div>
    </div>
</div>
</body>
</html>

7. Secure Login Form (login.php)

The login form validates the user's credentials against the hashed password stored in the database. It also includes the CSRF protection token.

login.php

<?php 
require_once("autoload.php");

if($getUser->is_loggedin()) {
    header("Location: account.php"); 
    exit();
}
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="utf-8">
    <title>Secure Login - Techno Smarter</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-5">
            <div class="card shadow-sm p-4">
                <h3 class="text-center mb-4">User Login</h3>
                
                <?php 
                if(isset($_GET['register']) && $_GET['register'] == 'success') {
                    echo '<div class="alert alert-success">Registration successful! Please log in.</div>'; 
                }
                
                if(isset($_POST['submit_form'])) {
                    // CSRF Validation
                    if (!hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) {
                        die('<div class="alert alert-danger">CSRF Validation Failed!</div>');
                    }

                    $login_var = $_POST['login_var']; 
                    $password = $_POST['password']; 
                    
                    if($getUser->login($login_var, $password)) {
                        header("Location: account.php");
                        exit();
                    } else {
                        echo '<div class="alert alert-danger">Invalid Username or Password!</div>';
                    }
                }
                ?>

                <form action="" method="POST">
                    <!-- CSRF Token Hidden Field -->
                    <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>">
                    
                    <div class="mb-3">
                        <label>Username or Email</label>
                        <input type="text" name="login_var" class="form-control" required>
                    </div>
                    
                    <div class="mb-3">
                        <label>Password</label>
                        <input type="password" name="password" class="form-control" required>
                    </div>
                    
                    <button type="submit" class="btn btn-primary w-100" name="submit_form">Login</button>
                </form>
                
                <p class="text-center mt-3">Don't have an account? <a href="signup.php">Sign up</a></p>
            </div>
        </div>
    </div>
</div>
</body>
</html>

8. Protected User Dashboard (account.php & logout.php)

Once logged in securely, the user is redirected to their dashboard. This page fetches their details securely using their active session ID.

account.php

<?php 
require_once("autoload.php");

// Restrict access to logged-in users only
if(!$getUser->is_loggedin()) {
    header("Location: login.php"); 
    exit();
}

// Fetch User Data
$userData = $getUser->fetch_user($_SESSION['userid']);
?>
<!DOCTYPE html>
<html lang="en">
<head>
    <title>My Dashboard</title>
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
</head>
<body class="bg-light">
<div class="container mt-5">
    <div class="row justify-content-center">
        <div class="col-md-6">
            <div class="card shadow-sm p-4">
                <div class="d-flex justify-content-between align-items-center mb-3">
                    <h4>Welcome, <?php echo htmlspecialchars($userData['fname']); ?>! ????</h4>
                    <a href="logout.php" class="btn btn-danger btn-sm">Logout</a>
                </div>
                <hr>
                <p><strong>Username:</strong> <?php echo htmlspecialchars($userData['username']); ?></p>
                <p><strong>Email Address:</strong> <?php echo htmlspecialchars($userData['email']); ?></p>
                <p><strong>Member Since:</strong> <?php echo $getUser->easy_date($userData['created_date']); ?></p>
            </div>
        </div>
    </div>
</div>
</body>
</html>

logout.php

<?php
require_once("autoload.php");
$getUser->logout();
header("Location: login.php");
exit();
?>

This is now a fully production-ready, modern, and highly secure PHP 8+ Login & Registration System. It protects your application against the most common OWASP vulnerabilities like SQLi, CSRF, and Session Hijacking.


Please Share

Recommended Posts:-