1721318544

Polly Password and the Magical Gatekeeper - Scure login system part2.


# <p align="center">Implementation Using PHP & SQL</p> ***1- Database and Table (SQL)*** I designed the `SecureLoginSystem` database to ensure a robust and secure user management system. I created the `users` table with columns for `id`, `username`, `email`, `password`, `role`, `otp_code`, `otp_expiry`, and `created_at`. This structure allows for unique identification of each user and securely stores passwords using the `password_hash` function, with roles defined to manage access control. Additionally, the `logs` table records user actions, storing the user’s `id`, action, `ip_address`, `user_agent`, and timestamp, which is crucial for auditing and tracking user activities. The `sessions` table is used to manage user sessions securely, storing the `user_id`, `session_id`, `created_at`, and `last_activity` timestamp. ```sql CREATE DATABASE SecureLoginSystem; USE SecureLoginSystem; -- Users table 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, role ENUM('administrator', 'admin', 'lawyer', 'staff', 'finance', 'reception') NOT NULL, otp_code VARCHAR(6), otp_expiry DATETIME, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ); -- Logs table CREATE TABLE logs ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, action VARCHAR(255) NOT NULL, ip_address VARCHAR(45), user_agent TEXT, timestamp TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); -- Sessions table CREATE TABLE sessions ( id INT AUTO_INCREMENT PRIMARY KEY, user_id INT NOT NULL, session_id VARCHAR(255) NOT NULL, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, last_activity TIMESTAMP DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (user_id) REFERENCES users(id) ); -- Dummy data INSERT INTO users (username, email, password, role) VALUES ('admin', 'admin@example.com', '$2y$10$e.ZbnWBOzYCBG/hm3RvWxOgyPQVUGiBbAoZ8cQs7E0WBxDqHZQ7t2', 'admin'), -- Password: Majdi@2024 ('lawyer1', 'lawyer1@example.com', '$2y$10$e.ZbnWBOzYCBG/hm3RvWxOgyPQVUGiBbAoZ8cQs7E0WBxDqHZQ7t2', 'lawyer'), -- Password: Majdi@2024 ('staff1', 'staff1@example.com', '$2y$10$e.ZbnWBOzYCBG/hm3RvWxOgyPQVUGiBbAoZ8cQs7E0WBxDqHZQ7t2', 'staff'), -- Password: Majdi@2024 ('finance1', 'finance1@example.com', '$2y$10$e.ZbnWBOzYCBG/hm3RvWxOgyPQVUGiBbAoZ8cQs7E0WBxDqHZQ7t2', 'finance'), -- Password: Majdi@2024 ('reception1', 'reception1@example.com', '$2y$10$e.ZbnWBOzYCBG/hm3RvWxOgyPQVUGiBbAoZ8cQs7E0WBxDqHZQ7t2', 'reception'); -- Password: Majdi@2024 ``` ***2- Main Page (ndex.php)*** In `index.php`, I implemented the user login functionality. I started by initializing a session and including the necessary configuration and function files. I then checked if the form was submitted and validated the CSRF token to prevent cross-site request forgery attacks. Additionally, I integrated Google reCAPTCHA to protect against automated login attempts. I sanitized the input for `username` and `password` to prevent SQL injection attacks and used the `validate_user` function to authenticate the user. If the credentials were valid, I generated a one-time password (OTP) and sent it to the user’s email using the `send_otp_email` function, ensuring an additional layer of security. ```php <?php // index.php session_start(); require 'config.php'; require 'functions.php'; // Check if the form is submitted if ($_SERVER['REQUEST_METHOD'] === 'POST') { // Check for the CSRF token if (!isset($_POST['csrf_token']) || !hash_equals($_SESSION['csrf_token'], $_POST['csrf_token'])) { die('CSRF token validation failed'); } // Validate captcha if (!isset($_POST['g-recaptcha-response']) || empty($_POST['g-recaptcha-response'])) { die('Captcha validation failed'); } // Validate login credentials $username = filter_input(INPUT_POST, 'username', FILTER_SANITIZE_STRING); $password = filter_input(INPUT_POST, 'password', FILTER_SANITIZE_STRING); if (validate_user($username, $password)) { // Generate and send OTP $otp = generate_otp($username); send_otp_email($username, $otp); $_SESSION['username'] = $username; header('Location: otp_verification.php'); exit(); } else { echo 'Invalid username or password'; } } // Generate CSRF token if (empty($_SESSION['csrf_token'])) { $_SESSION['csrf_token'] = bin2hex(random_bytes(32)); } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Login</title> <link rel="stylesheet" href="styles.css"> <script src="https://www.google.com/recaptcha/api.js" async defer></script> </head> <body> <form method="post" action=""> <input type="hidden" name="csrf_token" value="<?php echo $_SESSION['csrf_token']; ?>"> <label for="username">Username:</label> <input type="text" id="username" name="username" required> <label for="password">Password:</label> <input type="password" id="password" name="password" required> <div class="g-recaptcha" data-sitekey="your-site-key"></div> <button type="submit">Login</button> </form> </body> </html> ``` ***3- verification (otp_verification.php)*** In `otp_verification.php`, I implemented the OTP verification process. I verified that the user had previously authenticated by checking the session and then processed the OTP input. I sanitized the OTP and used the `verify_otp` function to check its validity against the stored value and expiry time. If the OTP was valid, I set the session `logged_in` status to true and redirected the user to the dashboard. ```php <?php // otp_verification.php session_start(); require 'config.php'; require 'functions.php'; if (!isset($_SESSION['username'])) { header('Location: index.php'); exit(); } if ($_SERVER['REQUEST_METHOD'] === 'POST') { $otp = filter_input(INPUT_POST, 'otp', FILTER_SANITIZE_STRING); if (verify_otp($_SESSION['username'], $otp)) { $_SESSION['logged_in'] = true; header('Location: dashboard.php'); exit(); } else { echo 'Invalid OTP'; } } ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>OTP Verification</title> <link rel="stylesheet" href="styles.css"> </head> <body> <form method="post" action=""> <label for="otp">OTP:</label> <input type="text" id="otp" name="otp" required> <button type="submit">Verify OTP</button> </form> </body> </html> ``` ***4- Welcome to The System (dashboard.php)*** For the `dashboard.php` page, I ensured that only authenticated users could access it by checking the `logged_in` session variable. If the user was not authenticated, they were redirected to the login page. The dashboard displayed a welcome message with the user’s sanitized username and provided a logout link. ```php <?php // dashboard.php session_start(); require 'config.php'; if (!isset($_SESSION['logged_in']) || $_SESSION['logged_in'] !== true) { header('Location: index.php'); exit(); } echo 'Welcome to the dashboard, ' . htmlspecialchars($_SESSION['username']); ?> <!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Dashboard</title> <link rel="stylesheet" href="styles.css"> </head> <body> <h1>Dashboard</h1> <p>Welcome, <?php echo htmlspecialchars($_SESSION['username']); ?>!</p> <a href="logout.php">Logout</a> </body> </html> ``` ***5- Go out (logout.php)*** In `logout.php`, I implemented the user logout functionality by destroying the session, which ensured that all session data was cleared, thereby preventing unauthorized access after logout. I added a `register.php` page to allow new users to register. This page included input fields for `username`, `email`, `password`, and `role`, and used a CSRF token and reCAPTCHA for security. I sanitized and validated the input data and used the `register_user` function to securely store the user information in the database, hashing the password with `password_hash`. ```php <?php // logout.php session_start(); session_unset(); session_destroy(); header('Location: index.php'); exit(); ?> ``` ***6- The Functions (functions.php)*** In `functions.php`, I defined several helper functions to handle database operations and security measures. The `connect_db` function established a connection to the database. The `validate_user` function authenticated users by checking the hashed password against the stored value. The `generate_otp` function created an OTP and stored it with an expiry time, while the `send_otp_email` function used PHPMailer to send the OTP to the user’s email securely. The `verify_otp` function validated the OTP, ensuring it was correct and had not expired. The `register_user` function securely stored new user data, hashing passwords to enhance security. check the code here: [functions.php](https://github.com/majdi-php-sql/login_registration_system/blob/main/functions.php) ***7- Connect to DB (config.php)*** In `config.php`, I configured the database connection parameters, ensuring a secure connection to the `SecureLoginSystem` database. The `styles.css` file provided a clean and user-friendly design for the forms, enhancing usability while maintaining a professional appearance. check the code here: [config.php](https://github.com/majdi-php-sql/login_registration_system/blob/main/config.php) ***Conclusion*** By implementing these comprehensive security measures, I ensured that the login system is highly secure and resilient against a wide range of cyber attacks. Finally I used phpmailer library to send the OTP to the user email. Good luck

(16) Comments
amargo85
amargo85
1721471260

```php // config.php $servername = "localhost"; $username = "root"; $password = ""; $dbname = "SecureLoginSystem"; $conn = new mysqli($servername, $username, $password, $dbname); if ($conn->connect_error) { die("Connection failed: " . $conn->connect_error); } ``` **PDO connections** are much more secure and in my opinion your code should be modified for this here👇 ```php $host = 'localhost'; $data = 'dbmyapp'; $user = 'user'; $pass = 'pass123'; $chrs = 'utf8mb4'; $attr = "mysql:host=$host;dbname=$data;charset=$chrs"; $opts = [ PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::ATTR_EMULATE_PREPARES => false, ]; try{ $pdo = new PDO($attr, $user, $pass, $opts); } catch(\PDOException $e){ throw new \PDOException($e->getMessage(), (int)$e->getCode()); } ```

majdi
majdi
1721473221

By the way, this comment should be in part 3, but it's all good.

majdi
majdi
1721473091

You are correct Using PDO is better but can you hack it as it is now, and how?

amargo85
amargo85
1721473453

I'm not talking about the attack itself, but also about: 1. Database abstraction: PDO offers a consistent interface for interacting with different types of databases (MySQL, PostgreSQL, SQLite, etc.), making it easy to switch from one database to another without having to rewrite the SQL code. 2. Flexibility: PDO allows you to execute transactions, manipulate large data sets and use different database drivers with a single interface. 3. Portability: Using PDO, it is possible to write code that works in various database management systems without significant changes.

majdi
majdi
1721474207

Meanwhile, let's focus on using the tools to find security vulnerabilities in part 3.

majdi
majdi
1721473945

What you say is correct regarding the state of the code currently in front of you, but this is part of a 12-part series where we’ll review all these details and more gradually. The purpose of publishing this series here is to: 1. Increase content. 2. Communicate ideas to students (aged 17 to 22, as mentioned in the first part) in detail and gradually, following a structured approach. 3. Discuss what you’re talking about now in Part 8 of this series, so that students understand the real reason for the existence of PDO and what it is, not just its features. This series isn’t for competition between experienced programmers, but to illustrate how to build an integrated security model. In PHP, there's PDO. What about Java or Python? Therefore, in Part 8, we’ll work to understand what PDO is, why it was added, and other related matters.


xReqX
xReqX
1721321381

aww no story this time?

majdi
majdi
1721321585

It was more than plenty

xReqX
xReqX
1721323040

hehe so there's not gonna be a sql?

majdi
majdi
1721323475

I'm sorry, I didn't catch that


amargo85
amargo85
1721319642

wouldn't it be better to create a function to sanitise the input fields? something more robust and complete. something more or less [like that](https://chat-to.dev/post?id=51)?

majdi
majdi
1721319932

Maybe in part 3 after we test the script using SQLmap, OWASP ZAP, Burp Suite, and Netsparker. After testing we are going to discover more issues and we will talk how to enhance the script.

amargo85
amargo85
1721320167

excellent! Would the methods used in these examples work for node.js, ruby and python programmers? or would the approach be completely different?

majdi
majdi
1721320689

We are discussing security patterns and best practices, which are generally language-agnostic principles that can be applied across different programming languages. While specific implementation details may vary based on the language and its ecosystem, these principles remain consistent. We may explore specific implementation nuances for different languages towards the conclusion of this series.

amargo85
amargo85
1721324256

it would be very important for users who have mastered other languages not to feel confused, especially beginners


Welcome to Chat-to.dev, a space for both novice and experienced programmers to chat about programming and share code in their posts.

About | Privacy | Terms | Donate
[2024 © Chat-to.dev]