Secure Registration and Login System with PHP and MySQL

The login system enables users to create and log in to their accounts, allowing them to access website content. The login system is a key feature for every membership website. If you want to restrict access to the website content and allow only the logged-in user to access the content, user login functionality needs to be implemented. User registration and login system can be integrated easily with PHP and MySQL. In this tutorial, we’ll show you how to build a secure login system with PHP and MySQL.

In this PHP Login System with MySQL (user authentication) script, we will implement the following features:

  • User Registration (first_name, last_name, email, password)
  • User Login (email, password)
  • User Logout
  • Forgot Password Recovery (email -> token emailed -> reset)
  • Account page showing logged-in user details
  • Session Management
  • Password Hashing
  • Input validation with error handling
  • CSRF Protection
  • Uses prepared statements (mysqli) to avoid SQL injection.

Before getting started to build User Login System with PHP, take a look at the complete file structure of the project.

login_system_with_php/
├── config.php
├── index.php
├── register.php
├── forgot.php
├── reset.php
├── account.php
├── logout.php
└── assets/
    └── style.css

Here is a brief description of the files in the project:

  • config.php – Contains database connection parameters and helper functions.
  • index.php – The login page where users can log in with their email and password.
  • register.php – The registration page where new users can create an account.
  • forgot.php – The page where users can request a password reset link.
  • reset.php – The page where users can reset their password using the token sent to their email.
  • account.php – The account page that displays logged-in user details.
  • logout.php – The page that logs out the user and destroys the session.

Create Database Table

Create a MySQL database (ex: codexworld_db) and a table named users using the following SQL query. The users table includes fields for user details such as first name, last name, email, password, and created date.

CREATE TABLE `users` (
  `id` INT UNSIGNED NOT NULL AUTO_INCREMENT,
  `first_name` VARCHAR(50) NOT NULL,
  `last_name` VARCHAR(50) NOT NULL,
  `email` VARCHAR(100) NOT NULL UNIQUE,
  `password` VARCHAR(255) NOT NULL,
  `reset_token` VARCHAR(255) DEFAULT NULL,
  `reset_expires` DATETIME DEFAULT NULL,
  `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

Database Configuration (config.php)

This file is used to set database connection parameters. Update the database configuration according to your MySQL setup. Also, set the base URL and mail settings for password recovery.

  • In the DB_HOST, DB_USER, DB_PASS, and DB_NAME constants, set the database connection parameters according to your MySQL setup.
  • In the BASE_URL constant, set the base URL according to your project path.
  • In the MAIL_FROM constant, set the email address from which password reset emails will be sent.

There are also helper functions to connect to the database, escape output, retrieve strings from POST data, and generate CSRF tokens for form submissions.

<?php 
declare(strict_types=1);

// Database configuration (adjust according to your mySQL setup)
const DB_HOST = 'localhost';
const 
DB_USER = 'root';
const 
DB_PASSWORD = '';
const 
DB_NAME = 'codexworld_db';

// Base URL (adjust if necessary)
const BASE_URL = 'http://localhost/secure_login_system_with_php/';

// Mail settings: when using localhost, ensure a mailer is configured or use SMTP in production
const MAIL_FROM = 'no-reply@example.com';

// Start session with secure settings
if (session_status() === PHP_SESSION_NONE) {
    
$https = isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== '' && $_SERVER['HTTPS'] !== 'off';
    
ini_set('session.use_strict_mode', '1');
    
ini_set('session.use_only_cookies', '1');
    
ini_set('session.use_trans_sid', '0');
    
session_set_cookie_params([
        
'lifetime' => 0,
        
'path' => '/',
        
'secure' => $https,
        
'httponly' => true,
        
'samesite' => 'Lax',
    ]);
    
session_start();
}

// Database connection function
function db_connect(): mysqli
{
    
mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);

    
$mysqli = new mysqli(DB_HOST, DB_USER, DB_PASSWORD, DB_NAME);
    
$mysqli->set_charset('utf8mb4');

    return 
$mysqli;
}

// Escape output for HTML
function e(string $value): string {
    return 
htmlspecialchars($value, ENT_QUOTES | ENT_SUBSTITUTE, 'UTF-8');
}

// Retrieve a string from POST data safely
function post_string(string $key): string {
    return isset(
$_POST[$key]) && is_string($_POST[$key]) ? $_POST[$key] : '';
}

// Generate a CSRF token and store it in the session
function csrf_token(): string {
    if (!isset(
$_SESSION['csrf_token']) || !is_string($_SESSION['csrf_token'])) {
        
$_SESSION['csrf_token'] = bin2hex(random_bytes(32));
    }
    return 
$_SESSION['csrf_token'];
}

// Validate CSRF token from POST request
function csrf_is_valid(): bool {
    
$submitted = post_string('csrf_token');
    return 
$submitted !== ''
        
&& isset($_SESSION['csrf_token'])
        && 
is_string($_SESSION['csrf_token'])
        && 
hash_equals($_SESSION['csrf_token'], $submitted);
}
?>

Registration Page (register.php)

This file contains the user registration form and handles the registration process. It includes input validation, password hashing, and storing user details in the database. It also checks for duplicate email addresses to prevent multiple registrations with the same email.

  • Includes configuration file.
  • If the user is already logged in (i.e., a session exists), they are redirected to the account page.
  • Initializes error and old input arrays to store validation errors and previously entered values.
  • Generates a CSRF token for form submission.
  • The form collects first name, last name, email, password, and password confirmation.
  • Handles form submission:
    • Checks if the request method is POST and validates the CSRF token.
    • Retrieves and trims form input values.
    • Stores old input values to repopulate the form in case of errors.
    • Validates input fields and populates the errors array if any validation fails.
    • Input validation checks for required fields, valid email format, password length, and matching passwords.
    • If validation passes, the password is hashed using password_hash() and the user details are inserted into the database using prepared statements to prevent SQL injection.
  • Upon successful registration, the user is redirected to the login page with a success message.
<?php 
declare(strict_types=1);

// Include configuration file
require_once 'config.php';

// If already logged in, redirect to account
if (!empty($_SESSION['user_id'])) {
    
header('Location: account.php');
    exit;
}

// Initialize
$errors = [];
$old = ['first_name'=>'','last_name'=>'','email'=>''];
$csrf = csrf_token();

// Handle registration form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !csrf_is_valid()) {
    
$errors['general'] = 'Your session expired. Please reload the page and try again.';
} elseif (
$_SERVER['REQUEST_METHOD'] === 'POST') {
    
$first = trim(post_string('first_name'));
    
$last = trim(post_string('last_name'));
    
$email = strtolower(trim(post_string('email')));
    
$password = post_string('password');
    
$confirm = post_string('password_confirm');
    
$old = ['first_name'=>$first,'last_name'=>$last,'email'=>$email];

    
// Validate input
    
if ($first === '') {
        
$errors['first_name'] = 'First name is required';
    } elseif (
mb_strlen($first, 'UTF-8') > 50) {
        
$errors['first_name'] = 'First name must be 50 characters or fewer';
    }
    if (
$last === '') {
        
$errors['last_name'] = 'Last name is required';
    } elseif (
mb_strlen($last, 'UTF-8') > 50) {
        
$errors['last_name'] = 'Last name must be 50 characters or fewer';
    }
    if (
$email === '') {
        
$errors['email'] = 'Email is required';
    } elseif (
strlen($email) > 100 || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        
$errors['email'] = 'Email is not valid';
    }
    if (
$password === '') {
        
$errors['password'] = 'Password is required';
    } elseif (
strlen($password) < 8) {
        
$errors['password'] = 'Password must be at least 8 characters';
    } elseif (
strlen($password) > 72) {
        
$errors['password'] = 'Password must be 72 bytes or fewer';
    }
    if (
$confirm !== $password) {
        
$errors['password_confirm'] = 'Passwords do not match';
    }

    if (empty(
$errors)) {
        
// Check if the email already exists in the database
        
$db = db_connect();
        
$stmt = $db->prepare('SELECT id FROM users WHERE email = ? LIMIT 1');
        
$stmt->bind_param('s', $email);
        
$stmt->execute();
        
$stmt->store_result();
        if (
$stmt->num_rows > 0) {
            
$errors['general'] = 'Unable to create an account with those details.';
        } else {
            
// Create the new user account
            
$hash = password_hash($password, PASSWORD_DEFAULT);
            
$ins = $db->prepare('INSERT INTO users (first_name,last_name,email,password) VALUES (?,?,?,?)');
            
$ins->bind_param('ssss', $first, $last, $email, $hash);
            try {
                
$inserted = $ins->execute();
            } catch (
mysqli_sql_exception $exception) {
                if (
$exception->getCode() !== 1062) {
                    throw 
$exception;
                }
                
$inserted = false;
            }
            
$ins->close();
            if (
$inserted) {
                
// Set a flash message and redirect to login (index.php)
                
$_SESSION['flash_success'] = 'Registration successful. You may now log in.';
                
header('Location: '.BASE_URL);
                exit;
            } else {
                
$errors['general'] = 'Unable to create an account with those details.';
            }
        }
        
$stmt->close();
        
$db->close();
    }
}
?> <div class="card"> <!-- Display error messages --> <?php if (isset($errors['general'])): ?> <div class="top-messages error" role="alert"> <strong>We couldn't create your account.</strong> <div><?php echo e($errors['general']); ?></div> </div> <?php endif; ?> <!-- Registration form --> <form method="post"> <input type="hidden" name="csrf_token" value="<?php echo e($csrf); ?>"> <div class="form-row"> <div class="form-group"> <label for="first_name">First name</label> <input type="text" id="first_name" name="first_name" value="<?php echo e($old['first_name']); ?>" required> <?php if (isset($errors['first_name'])): ?><small class="field-error" id="first_name_error"><?php echo e($errors['first_name']); ?></small><?php endif; ?> </div> <div class="form-group"> <label for="last_name">Last name</label> <input type="text" id="last_name" name="last_name" value="<?php echo e($old['last_name']); ?>" required> <?php if (isset($errors['last_name'])): ?><small class="field-error" id="last_name_error"><?php echo e($errors['last_name']); ?></small><?php endif; ?> </div> </div> <div class="form-group"> <label for="email_r">Email address</label> <input type="email" id="email_r" name="email" value="<?php echo e($old['email']); ?>" required> <?php if (isset($errors['email'])): ?><small class="field-error" id="email_error"><?php echo e($errors['email']); ?></small><?php endif; ?> </div> <div class="form-group"> <label for="password">Password</label> <input type="password" id="password" name="password" required> <?php if (isset($errors['password'])): ?><small class="field-error" id="password_error"><?php echo e($errors['password']); ?></small><?php endif; ?> </div> <div class="form-group"> <label for="password_confirm">Confirm password</label> <input type="password" id="password_confirm" name="password_confirm" required> <?php if (isset($errors['password_confirm'])): ?><small class="field-error" id="password_confirm_error"><?php echo e($errors['password_confirm']); ?></small><?php endif; ?> </div> <div class="form-actions"> <button type="submit" name="submit" class="btn">Create account</button> </div> </form> </div>

Login Form (index.php)

This is the login form that users will see when they visit the root URL of your application. It includes fields for email and password, as well as links to the registration page. The form submits to the same page (index.php) using the POST method.

The PHP code at the top of the file handles the following:

  • If the user is already logged in (i.e., a session variable for user_id is set), they are redirected to their account page.
  • On form submission, the email and password are retrieved from the POST data, and validate these credentials against the database. Password verification is done using password_verify().
  • If the credentials are valid, the user_id is stored in the session, and the user is redirected to their account page.
  • If the credentials are invalid, an error message is added to the errors array to be displayed.
  • If there are any error messages (e.g., from a failed login attempt), they are displayed at the top of the form.
  • If there is a success message (e.g., from a successful registration), it is also displayed at the top of the form.
<?php 
declare(strict_types=1);

// Include configuration file
require_once 'config.php';

// If already logged in, redirect to account
if (!empty($_SESSION['user_id'])) {
    
header('Location: account.php');
    exit;
}

// Initialize
$errors = [];
$old = ['email'=>''];
$csrf = csrf_token();

// show flash message from registration if set
$flash_success = '';
if (!empty(
$_SESSION['flash_success'])) {
    
$flash_success = $_SESSION['flash_success'];
    unset(
$_SESSION['flash_success']);
}

// Handle login form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !csrf_is_valid()) {
    
$errors['general'] = 'Your session expired. Please reload the page and try again.';
} elseif (
$_SERVER['REQUEST_METHOD'] === 'POST') {
    
$email = strtolower(trim(post_string('email')));
    
$password = post_string('password');
    
$old['email'] = $email;

    
// Validate input
    
if ($email === '') {
        
$errors['email'] = 'Email is required';
    } elseif (!
filter_var($email, FILTER_VALIDATE_EMAIL)) {
        
$errors['email'] = 'Email is not valid';
    }
    if (
$password === '') {
        
$errors['password'] = 'Password is required';
    } elseif (
strlen($password) > 72) {
        
$errors['password'] = 'Email or password is incorrect';
    }

    if (empty(
$errors)) {
        
// Prepare and execute the query to fetch user by email
        
$db = db_connect();
        
$stmt = $db->prepare('SELECT id, first_name, last_name, email, password FROM users WHERE email = ? LIMIT 1');
        
$stmt->bind_param('s', $email);
        
$stmt->execute();
        
$res = $stmt->get_result();
        if (
$user = $res->fetch_assoc()) {
            
// Verify the password and handle rehashing if necessary
            
if (password_verify($password, $user['password'])) {
                if (
password_needs_rehash($user['password'], PASSWORD_DEFAULT)) {
                    
$hash = password_hash($password, PASSWORD_DEFAULT);
                    
$update = $db->prepare('UPDATE users SET password = ? WHERE id = ?');
                    
$userId = (int) $user['id'];
                    
$update->bind_param('si', $hash, $userId);
                    
$update->execute();
                    
$update->close();
                }

                
// Regenerate session ID & store user ID in session & redirect to account page
                
session_regenerate_id(true);
                unset(
$_SESSION['csrf_token']);
                
$_SESSION['user_id'] = (int) $user['id'];
                
header('Location: account.php');
                exit;
            } else {
                
$errors['general'] = 'Email or password is incorrect';
            }
        } else {
            
$errors['general'] = 'Email or password is incorrect';
        }
        
$stmt->close();
        
$db->close();
    }
}
?> <div class="card"> <!-- Display error or success messages --> <?php if (isset($errors['general'])): ?> <div class="top-messages error" role="alert"> <strong>We couldn't sign you in.</strong> <div><?php echo e($errors['general']); ?></div> </div> <?php elseif (!empty($flash_success)): ?> <div class="top-messages success" role="status"> <strong>Account created</strong> <div><?php echo e($flash_success); ?></div> </div> <?php endif; ?> <!-- Login form --> <form method="post"> <input type="hidden" name="csrf_token" value="<?php echo e($csrf); ?>"> <div class="form-group"> <label for="email">Email address</label> <input id="email" name="email" type="email" value="<?php echo e($old['email']); ?>" required> <?php if (isset($errors['email'])): ?><small class="field-error" id="email_error"><?php echo e($errors['email']); ?></small><?php endif; ?> </div> <div class="form-group"> <label for="password_l">Password</label> <input id="password_l" name="password" type="password" required> <?php if (isset($errors['password'])): ?><small class="field-error" id="password_error"><?php echo e($errors['password']); ?></small><?php endif; ?> </div> <div class="form-actions"> <button type="submit" name="submit" class="btn">Login</button> </div> </form> </div>

Account Page (account.php)

This page is accessible only to logged-in users and displays their account information. It includes:

  • Check if the user is logged in by verifying the session.
  • If not logged in, redirect to the login page.
  • Fetch user details from the database using the user ID stored in the session.
  • Display user information such as first name, last name, email, and account creation date.
  • Provide a logout option that destroys the session and redirects to the login page.
<?php 
declare(strict_types=1);

// Include configuration file
require_once 'config.php';

// If not logged in, redirect to login
if (empty($_SESSION['user_id'])) {
    
header('Location: ' . BASE_URL);
    exit;
}

// Fetch user details
$db = db_connect();
$stmt = $db->prepare('SELECT id, first_name, last_name, email, created_at FROM users WHERE id = ? LIMIT 1');
$stmt->bind_param('i', $_SESSION['user_id']);
$stmt->execute();
$res = $stmt->get_result();
if (!
$user = $res->fetch_assoc()) {
    
// user missing, log out and redirect to login
    
$_SESSION = [];
    
header('Location: ' . BASE_URL);
    exit;
}
$stmt->close();
$db->close();
$csrf = csrf_token();
?> <div class="card"> <div class="account"> <h1>My account</h1> <form method="post" action="logout.php"> <input type="hidden" name="csrf_token" value="<?php echo e($csrf); ?>"> <button type="submit" class="btn secondary">Logout</button> </form> </div> <div class="account-details" aria-label="Account details"> <p><strong>First name:</strong> <?php echo e($user['first_name']); ?></p> <p><strong>Last name:</strong> <?php echo e($user['last_name']); ?></p> <p><strong>Email:</strong> <?php echo e($user['email']); ?></p> <p class="muted">Member since: <?php echo e($user['created_at']); ?></p> </div> </div>

Logout Page (logout.php)

This page logs the user out by destroying the session and redirects them to the login page.

  • Include the configuration file to access session settings.
  • Check CSRF token for security.
  • Clear the session data, destroy the session and clear cookies.
  • Redirect to the login page.
<?php 
declare(strict_types=1);

// Include configuration file
require_once 'config.php';

if (
$_SERVER['REQUEST_METHOD'] !== 'POST' || !csrf_is_valid()) {
    
http_response_code(403);
    exit(
'Invalid logout request.');
}

// Destroy session and clear cookies
$_SESSION = [];
if (
ini_get('session.use_cookies')) {
    
$params = session_get_cookie_params();
    
setcookie(session_name(), '', [
        
'expires' => time() - 42000,
        
'path' => $params['path'],
        
'domain' => $params['domain'],
        
'secure' => $params['secure'],
        
'httponly' => $params['httponly'],
        
'samesite' => $params['samesite'] ?? 'Lax',
    ]);
}
session_destroy();

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

Forgot Password Page (forgot.php)

This page allows users to request a password reset link by entering their email address. It includes:

  • Form with email input field and submit button.
  • CSRF token generation and validation for security.
  • Validation to ensure the email is provided and in a valid format.
  • Database check to verify if the email exists.
  • If the email exists, generate a unique token, store it in the database with an expiration time, and send a reset link to the user’s email. PHP mail() function can be used for sending emails.
  • Display success or error messages based on the outcome.
<?php 
declare(strict_types=1);

// Include configuration file
require_once 'config.php';

// Initialize
$errors = [];
$status = '';
$csrf = csrf_token();

// Handle forgot password form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !csrf_is_valid()) {
    
$errors['general'] = 'Your session expired. Please reload the page and try again.';
} elseif (
$_SERVER['REQUEST_METHOD'] === 'POST') {
    
$email = strtolower(trim(post_string('email')));
    if (
$email === '') {
        
$errors['email'] = 'Email is required';
    } elseif (
strlen($email) > 100 || !filter_var($email, FILTER_VALIDATE_EMAIL)) {
        
$errors['email'] = 'Email is not valid';
    }

    if (empty(
$errors)) {
        
// Check if the email exists in the database
        
$db = db_connect();
        
$stmt = $db->prepare('SELECT id, first_name FROM users WHERE email = ? LIMIT 1');
        
$stmt->bind_param('s', $email);
        
$stmt->execute();
        
$res = $stmt->get_result();
        if (
$user = $res->fetch_assoc()) {
            
// Generate a secure token and store its hash in the database with an expiration time
            
$token = bin2hex(random_bytes(32));
            
$tokenHash = hash('sha256', $token);
            
$expires = date('Y-m-d H:i:s', time() + 3600); // 1 hour
            
$up = $db->prepare('UPDATE users SET reset_token = ?, reset_expires = ? WHERE id = ?');
            
$userId = (int) $user['id'];
            
$up->bind_param('ssi', $tokenHash, $expires, $userId);
            if (
$up->execute()) {
                
// Send the reset password email with the token link
                
$resetLink = BASE_URL . 'reset.php?token=' . $token;
                
$subject = 'Password reset request';
                
$message = "Hello " . $user['first_name'] . ",\n\n" .
                    
"We received a request to reset your password. Click the link below to reset it:\n\n" .
                    
$resetLink . "\n\nIf you didn't request this, please ignore this email.\n\n" .
                    
"Regards\n" . MAIL_FROM;
                
$headers = 'From: ' . MAIL_FROM . "\r\n" . 'Reply-To: ' . MAIL_FROM . "\r\n";
                
// Try to send mail; on many dev environments this may not be configured. 
                
if (!mail($email, $subject, $message, $headers)) {
                    
error_log('Password reset email could not be sent.');
                }
            } else {
                
error_log('Password reset request could not be stored.');
            }
            
$up->close();
        }
        
$status = 'If the email exists in our system, a reset link has been sent.';
        
$stmt->close();
        
$db->close();
    }
}
?> <div class="card"> <!-- Display error or status messages --> <?php if (isset($errors['general'])): ?> <div class="top-messages error" role="alert"> <strong>We couldn't process that request.</strong> <div><?php echo e($errors['general']); ?></div> </div> <?php elseif ($status): ?> <div class="top-messages success" role="status"><?php echo e($status); ?></div> <?php endif; ?> <!-- Forgot password form --> <form method="post"> <input type="hidden" name="csrf_token" value="<?php echo e($csrf); ?>"> <div class="form-group"> <label for="email_f">Email address</label> <input type="email" id="email_f" name="email" required> <?php if (isset($errors['email'])): ?><small class="field-error" id="email_f_error"><?php echo e($errors['email']); ?></small><?php endif; ?> </div> <div class="form-actions"> <button type="submit" name="submit" class="btn">Send reset link</button> </div> </form> </div>

Reset Password Page (reset.php)

This page allows users to reset their password using the token sent to their email. It includes:

  • Retrieves the token from the URL.
  • Validates CSRF token.
  • Handles form submission:
    • Validates the new password and confirmation match.
    • Checks if the token is valid and not expired.
    • Updates the user’s password in the database.
    • Deletes the used token from the database.
    • Sets a success message upon successful password reset.
  • Displays any errors or status messages.
  • Provides a form for entering a new password and confirming it.
<?php 
declare(strict_types=1);

// Include configuration file
require_once 'config.php';

// Initialize
$errors = [];
$status = '';
$csrf = csrf_token();

// Get token from URL
$token = isset($_GET['token']) && is_string($_GET['token']) ? $_GET['token'] : '';

// Handle reset password form submission
if ($_SERVER['REQUEST_METHOD'] === 'POST' && !csrf_is_valid()) {
    
$errors['general'] = 'Your session expired. Please reload the page and try again.';
} elseif (
$_SERVER['REQUEST_METHOD'] === 'POST') {
    
$token = post_string('token');
    
$password = post_string('password');
    
$confirm = post_string('password_confirm');

    
// Validate input
    
if ($password === '') {
        
$errors['password'] = 'Password is required';
    } elseif (
strlen($password) < 8) {
        
$errors['password'] = 'Password must be at least 8 characters';
    } elseif (
strlen($password) > 72) {
        
$errors['password'] = 'Password must be 72 bytes or fewer';
    }
    if (
$confirm !== $password) {
        
$errors['password_confirm'] = 'Passwords do not match';
    }

    if (empty(
$errors) && preg_match('/\A[a-f0-9]{64}\z/', $token) === 1) {
        
// Check if the token exists and is valid in the database
        
$db = db_connect();
        
$tokenHash = hash('sha256', $token);
        
$stmt = $db->prepare('SELECT id, reset_expires FROM users WHERE reset_token = ? LIMIT 1');
        
$stmt->bind_param('s', $tokenHash);
        
$stmt->execute();
        
$res = $stmt->get_result();
        if (
$user = $res->fetch_assoc()) {
            if (
$user['reset_expires'] === null || strtotime($user['reset_expires']) < time()) {
                
$errors['general'] = 'This reset link is invalid or has expired.';
            } else {
                
// Update the user's password and clear the reset token and expiration
                
$hash = password_hash($password, PASSWORD_DEFAULT);
                
$up = $db->prepare('UPDATE users SET password = ?, reset_token = NULL, reset_expires = NULL WHERE id = ?');
                
$up->bind_param('si', $hash, $user['id']);
                if (
$up->execute()) {
                    
$status = 'Your password has been reset successfully. You may now log in.';
                } else {
                    
$errors['general'] = 'Unable to reset your password. Please try again later.';
                }
                
$up->close();
            }
        } else {
            
$errors['general'] = 'This reset link is invalid or has expired.';
        }
        
$stmt->close();
        
$db->close();
    } elseif (empty(
$errors)) {
        
$errors['general'] = 'This reset link is invalid or has expired.';
    }
}
?> <div class="card"> <!-- Display error or status messages --> <?php if (isset($errors['general'])): ?> <div class="top-messages error" role="alert"> <strong>We couldn't reset your password.</strong> <div><?php echo e($errors['general']); ?></div> </div> <?php elseif ($status): ?> <div class="top-messages success" role="status"><?php echo e($status); ?></div> <div class="form-actions"> <a class="btn" href="<?php echo e(BASE_URL); ?>">Go to login</a> </div> <?php endif; ?> <!-- Reset password form --> <?php if (empty($status)): ?> <form method="post"> <input type="hidden" name="csrf_token" value="<?php echo e($csrf); ?>"> <div class="form-group"> <label for="password">New password</label> <input type="password" id="password" name="password" required> <?php if (isset($errors['password'])): ?><small class="field-error" id="password_error"><?php echo e($errors['password']); ?></small><?php endif; ?> </div> <div class="form-group"> <label for="password_confirm">Confirm new password</label> <input type="password" id="password_confirm" name="password_confirm" required> <?php if (isset($errors['password_confirm'])): ?><small class="field-error" id="password_confirm_error"><?php echo e($errors['password_confirm']); ?></small><?php endif; ?> </div> <div class="form-actions"> <input type="hidden" name="token" value="<?php echo e($token); ?>"> <button type="submit" name="submit" class="btn">Reset password</button> </div> </form> <?php endif; ?> </div>

Conclusion

In this tutorial, we have created a simple user registration and login system using PHP and MySQL. We covered the following topics:

  • Setting up the database and creating the users table.
  • Creating a registration form (sign-up) and handling user input.
  • Implementing password hashing for security.
  • Creating a login form (sign-in) and validating user credentials.
  • Generating and sending password reset tokens via email.
  • Validating CSRF tokens to prevent cross-site request forgery attacks.
  • Using PHP sessions to manage user authentication.
  • Implementing a logout functionality.

By following these steps, you can build a basic user authentication system for your PHP web applications. Remember to always prioritize security by using prepared statements to prevent SQL injection and hashing passwords before storing them in the database.

To make this code functional, ensure you have a proper PHP environment set up with access to a MySQL database. You can further enhance this system by adding features like adding CSRF protection tokens to forms, using a proper mailer (PHPMailer) with SMTP credentials, email verification, forgot password with email OTP verification functionality, password reset, and user profile management.

You can purchase a complete and ready-to-use PHP User Login System Script from the Premium Scripts section or download the standard version from the Download Source Code button below.

Looking for expert assistance to implement or extend this script’s functionality? Submit a Service Request

28 Comments

  1. Eli Said...
  2. Tanjib Rubyat Said...
  3. Nathaniel Said...
  4. Vasman Said...
  5. Syed Ali Ahamad Said...
  6. Edwin Said...
  7. Ananth Said...
  8. Kilo Said...
  9. Quadryl Said...
    • CodexWorld Said...
  10. Narayana Said...
  11. Adam Said...
  12. Suhe Said...
  13. Abbi Said...
  14. Ryan Said...
  15. Rakim Said...
    • CodexWorld Said...
  16. Deepjyoti Baiahya Said...
  17. Kalai Said...
  18. Lawrence Igwegbe Said...
  19. Punith Said...
  20. Olli Said...
  21. Ion Vladescu Said...
  22. Shannu Said...
  23. Robin Tyagi Said...
  24. Tom Said...

Leave a reply

construction Need this implemented in your project? Request Implementation Help → keyboard_double_arrow_up