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
-
User Registration
-
Form validation
-
Password strength requirements
-
Email verification (optional)
-
Prevention of duplicate accounts
-
-
User Login
-
Secure session management
-
"Remember Me" functionality
-
Account lockout prevention
-
-
Password Recovery
-
Secure password reset via email
-
One-time use reset tokens
-
-
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;">
© <?=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:
-
Their details are validated
-
Password is hashed using password_hash()
-
An activation code is generated (if email verification is enabled)
-
Account is created in the database
-
Activation email is sent (if enabled)
2. Login Process
When a user logs in:
-
Credentials are validated
-
Password is verified using password_verify()
-
Session is created with user details
-
"Remember Me" cookie is set if requested
-
Last seen timestamp is updated
3. Password Reset Process
When a user requests password reset:
-
Email is validated
-
Reset token is generated and stored
-
Email with reset link is sent
-
When link is clicked, user can set new password
-
Reset token is invalidated after use
Security Considerations
-
SQL Injection Prevention
-
All database queries use prepared statements
-
-
Password Security
-
Passwords are hashed using bcrypt
-
Minimum password length requirement
-
-
Session Security
-
Custom session name
-
Secure session cookie settings
-
Session regeneration on login
-
-
XSS Prevention
-
Output is escaped using htmlspecialchars()
-
-
CSRF Protection
-
Could be added with hidden form tokens
-
Implementation Notes
To implement this system on your website:
-
Create the database and table as shown above
-
Update the database credentials in includes/config.php
-
Configure the email settings for activation and password reset emails
-
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



COMMENTS
No comments...