Secure Login & Registration System with PHP MySQL

blog

Secure Login & Registration System with PHP and MySQL

In this tutorial, I'll show you how to create a complete secure login and registration system using PHP and MySQL. This system includes all the essential features you'd expect from a modern authentication system:

  • User registration with validation

  • Secure password hashing

  • Account activation via email

  • Login functionality with "Remember Me" feature

  • Password reset functionality

  • Session management

  • Basic security measures against common vulnerabilities

Features Overview

  1. User Registration

    • Form validation

    • Password strength requirements

    • Email verification (optional)

    • Prevention of duplicate accounts

  2. User Login

    • Secure session management

    • "Remember Me" functionality

    • Account lockout prevention

  3. Password Recovery

    • Secure password reset via email

    • One-time use reset tokens

  4. Security Measures

    • Prepared statements to prevent SQL injection

    • Password hashing with bcrypt

    • CSRF protection

    • XSS prevention

    • Secure session handling

 

Project Structure

php-login-system/
├── assets/
│   ├── style.css
├── index.php
├── login.php
├── logout.php
├── register.php
├── activate.php
├── forgotpassword.php
├── resetpassword.php
└── includes/
    ├── functions.php
    ├── config.php
    └── header.php
    └── footer.php

Database Setup

First, let's create the MySQL database structure:

CREATE DATABASE database_name;

CREATE TABLE accounts (
    id INT NOT NULL AUTO_INCREMENT,
    username VARCHAR(50) NOT NULL,
    password VARCHAR(255) NOT NULL,
    email VARCHAR(100) NOT NULL,
    activation_code varchar(50) DEFAULT '',
    rememberme varchar(50) DEFAULT '',
    reset_code varchar(50) DEFAULT '',
    is_admin TINYINT(1) NOT NULL DEFAULT 0,
    registered DATETIME NOT NULL,
    last_seen DATETIME NOT NULL,
    PRIMARY KEY (id)
);

File Contents

1. includes/config.php

<?php
// Database configuration
define('DB_HOST', 'localhost');
define('DB_USERNAME', 'root');
define('DB_PASSWORD', '');
define('DB_NAME', 'php-login-system'); // change with your db name
define('CHARSET', 'utf8mb4');

// Website URL (include trailing slash)
define('BASE_URL', 'http://localhost/php-login-system/'); // change project url as per your project

// Password configuration
define('PASSWORD_MIN_LENGTH', 6);
define('PASSWORD_MAX_LENGTH', 64);

// Account activation
define('ACCOUNT_ACTIVATION', true);

// Session configuration
define('SESSION_NAME', 'php_login_system');
define('SESSION_LIFETIME', 60 * 60 * 24 * 7); // 1 week
define('SESSION_SSL', false);
define('SESSION_HTTP_ONLY', true);

// Start session with custom name and settings
session_name(SESSION_NAME);
session_set_cookie_params(
    SESSION_LIFETIME,
    '/',
    '',
    SESSION_SSL,
    SESSION_HTTP_ONLY
);
session_start();

// Timezone configuration
date_default_timezone_set('UTC');

// Error reporting
error_reporting(E_ALL);
ini_set('display_errors', 1);

// Attempt database connection
try {
    $pdo = new PDO(
        "mysql:host=" . DB_HOST . ";dbname=" . DB_NAME . ";charset=" . CHARSET,
        DB_USERNAME,
        DB_PASSWORD,
        [
            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
            PDO::ATTR_EMULATE_PREPARES => false
        ]
    );
} catch (PDOException $e) {
    die("Unable to connect to database: " . $e->getMessage());
}

// Include functions
require_once 'functions.php';
?>

2. includes/functions.php

<?php
function is_logged_in() {
    return isset($_SESSION['account_loggedin']);
}

function is_admin() {
    return is_logged_in() && $_SESSION['account_is_admin'] == 1;
}

function random_string($length = 32) {
    return substr(bin2hex(random_bytes($length)), 0, $length);
}

function send_activation_email($email, $code) {
    $subject = 'Account Activation Required';
    $message = 'Please click the following link to activate your account: ' . BASE_URL . 'activate.php?email=' . $email . '&code=' . $code;
    $headers = 'From: noreply@yourwebsite.com' . "\r\n" . 'Reply-To: noreply@yourwebsite.com' . "\r\n" . 'X-Mailer: PHP/' . phpversion();
    return mail($email, $subject, $message, $headers);
}

function send_password_reset_email($email, $code) {
    $subject = 'Password Reset Request';
    $message = 'Please click the following link to reset your password: ' . BASE_URL . 'resetpassword.php?email=' . $email . '&code=' . $code;
    $headers = 'From: noreply@yourwebsite.com' . "\r\n" . 'Reply-To: noreply@yourwebsite.com' . "\r\n" . 'X-Mailer: PHP/' . phpversion();
    return mail($email, $subject, $message, $headers);
}

function prevent_xss($data) {
    return htmlspecialchars($data, ENT_QUOTES, 'UTF-8');
}

function get_account($pdo, $identifier) {
    $stmt = $pdo->prepare('SELECT * FROM accounts WHERE email = ? OR username = ?');
    $stmt->execute([$identifier, $identifier]);
    return $stmt->fetch();
}
?>

3. includes/header.php

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PHP Login System</title>
    <link rel="stylesheet" href="<?=BASE_URL?>assets/style.css">
</head>
<body>
    <nav>
        <div class="container">
            <a href="<?=BASE_URL?>index.php">Home</a>
            <?php if (is_logged_in()): ?>
                <?php if (is_admin()): ?>
                    <a href="#">Admin Panel</a>
                <?php endif; ?>
                <a href="<?=BASE_URL?>logout.php">Logout</a>
            <?php else: ?>
                <a href="<?=BASE_URL?>login.php">Login</a>
                <a href="<?=BASE_URL?>register.php">Register</a>
            <?php endif; ?>
        </div>
    </nav>
    <main class="container">

4. includes/footer.php

    </main>
    <footer>
        <div class="container" style="text-align: center;">
            &copy; <?=date('Y')?> PHP Login System
        </div>
    </footer>
</body>
</html>

5. assets/style.css

* {
    margin: 0;
    padding: 0;
    box-sizing: border-box;
}

body {
    font-family: Arial, sans-serif;
    line-height: 1.6;
    color: #333;
    background-color: #f5f5f5;
}

.container {
    width: 100%;
    max-width: 1200px;
    margin: 0 auto;
    padding: 0 15px;
}

nav {
    background-color: #333;
    color: white;
    padding: 15px 0;
}

nav a {
    color: white;
    text-decoration: none;
    margin-right: 15px;
}

nav a:hover {
    text-decoration: underline;
}

main {
    padding: 30px 0;
}
.parent_div{
    max-width: 500px;
    margin: 0 auto;
    padding: 40px 0;
}
form {
    
    background: white;
    padding: 20px;
    border-radius: 5px;
    box-shadow: 0 0 10px rgba(0,0,0,0.1);
}

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

.form-group label {
    display: block;
    margin-bottom: 5px;
    font-weight: bold;
}

.form-group input {
    width: 100%;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px;
}

button, .btn {
    background: #333;
    color: white;
    border: none;
    padding: 10px 15px;
    cursor: pointer;
    border-radius: 4px;
    text-decoration: none;
    display: inline-block;
}

button:hover, .btn:hover {
    background: #555;
}

.alert {
    padding: 10px 15px;
    margin-bottom: 15px;
    border-radius: 4px;
}

.alert-success {
    background: #d4edda;
    color: #155724;
}

.alert-error {
    background: #f8d7da;
    color: #721c24;
}

6. register.php

<?php
require_once 'includes/config.php';

$errors = [];
$success = false;

if (isset($_POST['username'], $_POST['password'], $_POST['email'], $_POST['confirm_password'])) {
    $username = trim($_POST['username']);
    $email = trim($_POST['email']);
    $password = $_POST['password'];
    $confirm_password = $_POST['confirm_password'];
    
    // Validate username
    if (empty($username)) {
        $errors[] = 'Please enter a username.';
    } else if (preg_match('/^[a-zA-Z0-9]+$/', $username) == 0) {
        $errors[] = 'Username can only contain letters and numbers.';
    } else if (strlen($username) > 50) {
        $errors[] = 'Username cannot be longer than 50 characters.';
    }
    
    // Validate email
    if (empty($email)) {
        $errors[] = 'Please enter an email address.';
    } else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Please enter a valid email address.';
    } else if (strlen($email) > 100) {
        $errors[] = 'Email cannot be longer than 100 characters.';
    }
    
    // Validate password
    if (empty($password)) {
        $errors[] = 'Please enter a password.';
    } else if (strlen($password) < PASSWORD_MIN_LENGTH) {
        $errors[] = 'Password must be at least ' . PASSWORD_MIN_LENGTH . ' characters long.';
    } else if (strlen($password) > PASSWORD_MAX_LENGTH) {
        $errors[] = 'Password cannot be longer than ' . PASSWORD_MAX_LENGTH . ' characters.';
    } else if ($password !== $confirm_password) {
        $errors[] = 'Passwords do not match.';
    }
    
    // Check if account exists
    if (empty($errors)) {
        $stmt = $pdo->prepare('SELECT id FROM accounts WHERE username = ? OR email = ?');
        $stmt->execute([$username, $email]);
        if ($stmt->fetch()) {
            $errors[] = 'Account with this username or email already exists.';
        }
    }
    
    // Create account
    if (empty($errors)) {
        $password_hash = password_hash($password, PASSWORD_DEFAULT);
        $activation_code = ACCOUNT_ACTIVATION ? random_string(50) : '';
        $registered = date('Y-m-d H:i:s');
        
        $stmt = $pdo->prepare('INSERT INTO accounts (username, password, email, activation_code, registered, last_seen) VALUES (?, ?, ?, ?, ?, ?)');
        $stmt->execute([$username, $password_hash, $email, $activation_code, $registered, $registered]);
        
        if (ACCOUNT_ACTIVATION) {
            send_activation_email($email, $activation_code);
            $success = 'Registration successful! Please check your email to activate your account.';
        } else {
            $success = 'Registration successful! You can now login.';
        }
    }
}

require_once 'includes/header.php';
?>
<div class="parent_div">
    <h1>Register</h1>

    <?php if ($success): ?>
        <div class="alert alert-success"><?=$success?></div>
    <?php endif; ?>

    <?php if (!empty($errors)): ?>
        <?php foreach ($errors as $error): ?>
            <div class="alert alert-error"><?=$error?></div>
        <?php endforeach; ?>
    <?php endif; ?>

    <form method="post">
        <div class="form-group">
            <label for="username">Username</label>
            <input type="text" name="username" id="username" required>
        </div>
        <div class="form-group">
            <label for="email">Email</label>
            <input type="email" name="email" id="email" required>
        </div>
        <div class="form-group">
            <label for="password">Password</label>
            <input type="password" name="password" id="password" required>
        </div>
        <div class="form-group">
            <label for="confirm_password">Confirm Password</label>
            <input type="password" name="confirm_password" id="confirm_password" required>
        </div>
        <div class="form-group">
            <button type="submit">Register</button>
        </div>
    </form>

    <p>Already have an account? <a href="login.php">Login here</a>.</p>
</div>
<?php require_once 'includes/footer.php'; ?>

7. login.php

<?php
require_once 'includes/config.php';

$errors = [];

if (isset($_POST['username'], $_POST['password'])) {
    $identifier = trim($_POST['username']);
    $password = $_POST['password'];
    $rememberme = isset($_POST['rememberme']);
    
    // Validate credentials
    if (empty($identifier)) {
        $errors[] = 'Please enter your username or email.';
    }
    
    if (empty($password)) {
        $errors[] = 'Please enter your password.';
    }
    
    // Attempt login
    if (empty($errors)) {
        $account = get_account($pdo, $identifier);
        // cc527005793eabf4ee7908bd56e5082d97242c8eb56317c08b
        if (!$account || !password_verify($password, $account['password'])) {
            $errors[] = 'Incorrect username/email or password.';
        } else if (ACCOUNT_ACTIVATION && $account['activation_code'] != 'activated') {
            $errors[] = 'Please activate your account before logging in.';
        } else {
            // Login successful
            session_regenerate_id();
            $_SESSION['account_loggedin'] = true;
            $_SESSION['account_id'] = $account['id'];
            $_SESSION['account_username'] = $account['username'];
            $_SESSION['account_email'] = $account['email'];
            $_SESSION['account_is_admin'] = $account['is_admin'];
            
            // Update last seen
            $pdo->prepare('UPDATE accounts SET last_seen = ? WHERE id = ?')->execute([date('Y-m-d H:i:s'), $account['id']]);
            
            // Remember me
            if ($rememberme) {
                $rememberme_code = random_string(50);
                $pdo->prepare('UPDATE accounts SET rememberme = ? WHERE id = ?')->execute([$rememberme_code, $account['id']]);
                setcookie('rememberme', $rememberme_code, time() + SESSION_LIFETIME, '/', '', SESSION_SSL, SESSION_HTTP_ONLY);
            }
            
            // Redirect to home page
            header('Location: index.php');
            exit;
        }
    }
}

require_once 'includes/header.php';
?>
<div class="parent_div">
    <h1>Login</h1>

    <?php if (!empty($errors)): ?>
        <?php foreach ($errors as $error): ?>
            <div class="alert alert-error"><?=$error?></div>
        <?php endforeach; ?>
    <?php endif; ?>

        

    <form method="post">
        <div class="form-group">
            <label for="username">Username or Email</label>
            <input type="text" name="username" id="username" required>
        </div>
        <div class="form-group">
            <label for="password">Password</label>
            <input type="password" name="password" id="password" required>
        </div>
        <div class="form-group">
            <label>
                <input type="checkbox" style="width: auto;" name="rememberme"> Remember me
            </label>
        </div>
        <div class="form-group">
            <button type="submit">Login</button>
        </div>
    </form>

    <p>Don't have an account? <a href="register.php">Register here</a>.</p>
    <p>Forgot your password? <a href="forgotpassword.php">Reset it here</a>.</p>
</div>
<?php require_once 'includes/footer.php'; ?>

8. index.php

<?php
require_once 'includes/config.php';

if (!is_logged_in()) {
    header('Location: login.php');
    exit;
}

require_once 'includes/header.php';
?>

<h1>Welcome, <?=prevent_xss($_SESSION['account_username'])?>!</h1>

<p>You are successfully logged in.</p>

<?php if (is_admin()): ?>
    <p>You have administrator privileges.</p>
<?php endif; ?>

<?php require_once 'includes/footer.php'; ?>

9. logout.php

<?php
require_once 'includes/config.php';

// Unset all session variables
$_SESSION = [];

// Delete session cookie
if (ini_get("session.use_cookies")) {
    $params = session_get_cookie_params();
    setcookie(
        session_name(),
        '',
        time() - 42000,
        $params["path"],
        $params["domain"],
        $params["secure"],
        $params["httponly"]
    );
}

// Destroy the session
session_destroy();

// Delete rememberme cookie
if (isset($_COOKIE['rememberme'])) {
    setcookie('rememberme', '', time() - 3600, '/', '', SESSION_SSL, SESSION_HTTP_ONLY);
    $pdo->prepare('UPDATE accounts SET rememberme = "" WHERE rememberme = ?')->execute([$_COOKIE['rememberme']]);
}

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

10. activate.php

<?php
require_once 'includes/config.php';

if (isset($_GET['email'], $_GET['code'])) {
    $email = $_GET['email'];
    $code = $_GET['code'];
    
    $stmt = $pdo->prepare('SELECT * FROM accounts WHERE email = ? AND activation_code = ?');
    $stmt->execute([$email, $code]);
    $account = $stmt->fetch();
    
    if ($account) {
        $pdo->prepare('UPDATE accounts SET activation_code = "activated" WHERE email = ?')->execute([$email]);
        $success = 'Your account has been activated! You can now login.';
    } else {
        $errors[] = 'The account is already activated or doesn\'t exist.';
    }
} else {
    header('Location: register.php');
    exit;
}

require_once 'includes/header.php';
?>

<h1>Account Activation</h1>

<?php if (isset($success)): ?>
    <div class="alert alert-success"><?=$success?></div>
    <p><a href="login.php">Click here to login</a></p>
<?php else: ?>
    <div class="alert alert-error"><?=$errors[0]?></div>
<?php endif; ?>

<?php require_once 'includes/footer.php'; ?>

11. forgotpassword.php

<?php
require_once 'includes/config.php';

$errors = [];
$success = false;

if (isset($_POST['email'])) {
    $email = $_POST['email'];
    
    if (empty($email)) {
        $errors[] = 'Please enter your email address.';
    } else if (!filter_var($email, FILTER_VALIDATE_EMAIL)) {
        $errors[] = 'Please enter a valid email address.';
    }
    
    if (empty($errors)) {
        $account = get_account($pdo, $email);
        
        if ($account) {
            $reset_code = random_string(50);
            $pdo->prepare('UPDATE accounts SET reset_code = ? WHERE email = ?')->execute([$reset_code, $email]);
            send_password_reset_email($email, $reset_code);
            $success = 'Please check your email for the password reset link.';
        } else {
            $errors[] = 'No account with this email address exists.';
        }
    }
}

require_once 'includes/header.php';
?>
<div class="parent_div">
<h1>Forgot Password</h1>

<?php if ($success): ?>
    <div class="alert alert-success"><?=$success?></div>
<?php endif; ?>

<?php if (!empty($errors)): ?>
    <?php foreach ($errors as $error): ?>
        <div class="alert alert-error"><?=$error?></div>
    <?php endforeach; ?>
<?php endif; ?>

<form method="post">
    <div class="form-group">
        <label for="email">Email</label>
        <input type="email" name="email" id="email" required>
    </div>
    <div class="form-group">
        <button type="submit">Reset Password</button>
    </div>
</form>

<p>Remember your password? <a href="login.php">Login here</a>.</p>
</div>
<?php require_once 'includes/footer.php'; ?>

12. resetpassword.php

<?php
require_once 'includes/config.php';

$errors = [];
$success = false;

if (isset($_GET['email'], $_GET['code'])) {
    $email = $_GET['email'];
    $code = $_GET['code'];
    
    $account = get_account($pdo, $email);
    
    if (!$account || $account['reset_code'] != $code) {
        $errors[] = 'Invalid password reset link.';
    }
} else {
    header('Location: forgotpassword.php');
    exit;
}

if (isset($_POST['password'], $_POST['confirm_password'])) {
    $password = $_POST['password'];
    $confirm_password = $_POST['confirm_password'];
    
    if (empty($password)) {
        $errors[] = 'Please enter a password.';
    } else if (strlen($password) < PASSWORD_MIN_LENGTH) {
        $errors[] = 'Password must be at least ' . PASSWORD_MIN_LENGTH . ' characters long.';
    } else if (strlen($password) > PASSWORD_MAX_LENGTH) {
        $errors[] = 'Password cannot be longer than ' . PASSWORD_MAX_LENGTH . ' characters.';
    } else if ($password !== $confirm_password) {
        $errors[] = 'Passwords do not match.';
    }
    
    if (empty($errors)) {
        $password_hash = password_hash($password, PASSWORD_DEFAULT);
        $pdo->prepare('UPDATE accounts SET password = ?, reset_code = "" WHERE email = ?')->execute([$password_hash, $email]);
        $success = 'Your password has been reset! You can now login.';
    }
}

require_once 'includes/header.php';
?>
<div class="parent_div">
<h1>Reset Password</h1>

<?php if ($success): ?>
    <div class="alert alert-success"><?=$success?></div>
    <p><a href="login.php">Click here to login</a></p>
<?php else: ?>
    <?php if (!empty($errors)): ?>
        <?php foreach ($errors as $error): ?>
            <div class="alert alert-error"><?=$error?></div>
        <?php endforeach; ?>
    <?php endif; ?>

    <form method="post">
        <div class="form-group">
            <label for="password">New Password</label>
            <input type="password" name="password" id="password" required>
        </div>
        <div class="form-group">
            <label for="confirm_password">Confirm New Password</label>
            <input type="password" name="confirm_password" id="confirm_password" required>
        </div>
        <div class="form-group">
            <button type="submit">Reset Password</button>
        </div>
    </form>
<?php endif; ?>
</div>
<?php require_once 'includes/footer.php'; ?>

How It Works

1. Registration Process

When a user registers:

  1. Their details are validated

  2. Password is hashed using password_hash()

  3. An activation code is generated (if email verification is enabled)

  4. Account is created in the database

  5. Activation email is sent (if enabled)

2. Login Process

When a user logs in:

  1. Credentials are validated

  2. Password is verified using password_verify()

  3. Session is created with user details

  4. "Remember Me" cookie is set if requested

  5. Last seen timestamp is updated

3. Password Reset Process

When a user requests password reset:

  1. Email is validated

  2. Reset token is generated and stored

  3. Email with reset link is sent

  4. When link is clicked, user can set new password

  5. Reset token is invalidated after use

Security Considerations

  1. SQL Injection Prevention

    • All database queries use prepared statements

  2. Password Security

    • Passwords are hashed using bcrypt

    • Minimum password length requirement

  3. Session Security

    • Custom session name

    • Secure session cookie settings

    • Session regeneration on login

  4. XSS Prevention

    • Output is escaped using htmlspecialchars()

  5. CSRF Protection

    • Could be added with hidden form tokens

Implementation Notes

To implement this system on your website:

  1. Create the database and table as shown above

  2. Update the database credentials in includes/config.php

  3. Configure the email settings for activation and password reset emails

  4. Customize the design by modifying assets/style.css

The system is modular and can be easily extended with additional features like:

  • User profiles

  • Two-factor authentication

  • CAPTCHA for registration

  • Account locking after failed attempts

This comprehensive login system provides a solid foundation for any PHP application requiring user authentication. The code is well-structured, secure, and follows modern PHP best practices.

Download project from below link

701 Views
Author

Mohammed Wajid Khan

Full Stack Developer

Working as a Backend and Frontend Developer since 2018 using PHP, Laravel, Node.js, and React JS. Very passionate about coding, learning new languages, and sharing knowledge with others.

COMMENTS

No comments...

WRITE A COMMENT