How to Add a Login System With Remember Me and Session Timeout to Any PHP Website – 2026

Build This Feature PHP + MySQL Secure Login Remember Me Cookie Session Timeout Security Guide 2026
Build This Feature — PHP Tutorial Series

How to Add a Login System With Remember Me and Session Timeout to Any PHP Website — 2026

Every PHP management system needs a login. But most student login systems have two critical missing features: a “Remember Me” checkbox that keeps users logged in safely, and a session timeout that logs idle users out automatically. This tutorial adds both to any existing PHP project — securely, from scratch, with complete working code.

🔒 Secure cookie token system ⏱ Idle session timeout ✅ Full working code 🛡 Security checklist

The default login systems in most PHP student projects have the same problem: they work, but they are brittle. Close the browser and you are logged out immediately — there is no “Remember Me” option. Leave the system idle for 20 minutes and you are still logged in — there is no timeout. Neither is how real applications behave.

This tutorial builds both features correctly and securely. The “Remember Me” section uses a cryptographically secure token stored in the database — not the dangerous approach of storing the user ID in a cookie. The session timeout section uses a last-activity timestamp to automatically redirect idle users to the login page.

⛔ The insecure “Remember Me” approach you must avoid: Many tutorials suggest storing the user ID directly in a cookie: setcookie('remember_user', $user_id). This is a serious vulnerability — any user can change their cookie value to 1 (which is usually the admin ID) and gain admin access to your system. Never do this. The correct approach uses a random token stored in the database, which is what this tutorial implements.
1Database Table
2Login Form (HTML)
3Login Logic (PHP)
4Remember Me Cookie
5Session Timeout
6Auth Check + Logout

What You Will Build — File Structure

your-project/ ├── config/dbconnection.php (your existing database connection) ├── auth/ ← NEW folder │ ├── login.php ← NEW: login form + processing │ ├── logout.php ← NEW: logout + clear cookie │ └── auth_check.php ← NEW: include on every protected page └── admin/dashboard.php (your existing protected pages — add 1 line)

Once built, every protected page in your system only needs this one line added at the very top: <?php require_once '../auth/auth_check.php'; ?>


Step 1 — Add the Remember Me Token Table to Your Database

Open phpMyAdmin, select your project’s database, go to the SQL tab, and run this query. It creates a table that stores the “Remember Me” tokens alongside your existing users table.

SQL Run in phpMyAdmin — adds remember_tokens table

-- Run this in phpMyAdmin SQL tab to add Remember Me support
-- Your existing users table stays completely unchanged
CREATE TABLE IF NOT EXISTS `remember_tokens` (
    `id`         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    `user_id`    INT UNSIGNED NOT NULL,
    `token_hash` VARCHAR(64) NOT NULL,      -- SHA256 hash of the random token
    `expires_at` DATETIME NOT NULL,          -- Token expiry (30 days from creation)
    `created_at` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_user    (`user_id`),
    INDEX idx_token   (`token_hash`),
    CONSTRAINT fk_rt_user FOREIGN KEY (`user_id`)
        REFERENCES `users`(`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

-- Also make sure your users table has a password_hash column
-- (not plain text password!). If not, you need to update your passwords:
-- ALTER TABLE users ADD COLUMN password_hash VARCHAR(255) AFTER username;

Add password hashing to your existing users (if not already done)

If your system currently stores plain text passwords, this is the moment to fix it. Never store plain text passwords. Here is how to update your existing users to use PHP’s secure password_hash() function:

PHP Run once to convert plain text passwords to secure hashes

// run_once_hash_passwords.php — delete this file after running it once!
require_once '../config/dbconnection.php';
$result = mysqli_query($conn, "SELECT id, password FROM users");
while ($row = mysqli_fetch_assoc($result)) {
    $hash = password_hash($row['password'], PASSWORD_DEFAULT);
    $stmt = mysqli_prepare($conn, "UPDATE users SET password_hash=? WHERE id=?");
    mysqli_stmt_bind_param($stmt, 'si', $hash, $row['id']);
    mysqli_stmt_execute($stmt);
}
echo 'All passwords hashed successfully. DELETE THIS FILE NOW.';

Step 2 — The Login Form (HTML)

HTML auth/login.php — login form with Remember Me checkbox

<!-- auth/login.php -- top of file has PHP processing logic (see Step 3) -->
<!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="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css">
</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">
        <div class="card-header bg-primary text-white text-center">
          <h5 class="mb-0">🔐 Login</h5>
        </div>
        <div class="card-body">
          <!-- Show error message if login failed -->
          <?php if (!empty($error)): ?>
            <div class="alert alert-danger"><?= htmlspecialchars($error) ?></div>
          <?php endif; ?>

          <form method="POST" action="">
            <div class="mb-3">
              <label class="form-label">Username or Email</label>
              <input type="text" name="username" class="form-control" required
                     value="<?= htmlspecialchars($_POST['username'] ?? '') ?>">
            </div>
            <div class="mb-3">
              <label class="form-label">Password</label>
              <input type="password" name="password" class="form-control" required>
            </div>
            <div class="mb-3 form-check">
              <!-- The Remember Me checkbox -->
              <input type="checkbox" class="form-check-input" name="remember_me" id="rememberMe">
              <label class="form-check-label" for="rememberMe">
                Keep me logged in for 30 days
              </label>
            </div>
            <button type="submit" class="btn btn-primary w-100">Log In</button>
          </form>
        </div>
      </div>
    </div>
  </div>
</div>
</body></html>

Step 3 — The Login Processing Logic (PHP)

Add this PHP block to the very top of auth/login.php, before the HTML. This handles the form submission, verifies credentials, starts the session, and sets the Remember Me cookie if the checkbox was ticked.

PHP Add to the very top of auth/login.php — before any HTML

<?php
session_start();
require_once '../config/dbconnection.php';

$error = '';
$SESSION_TIMEOUT = 1800;   // 30 minutes idle timeout (in seconds)
$REMEMBER_DAYS  = 30;     // Remember Me cookie lifetime in days

// If already logged in via session, redirect to dashboard
if (isset($_SESSION['user_id'])) {
    header('Location: ../admin/dashboard.php'); exit();
}

// Check for valid Remember Me cookie first
if (isset($_COOKIE['remember_me'])) {
    $raw_token  = $_COOKIE['remember_me'];
    $token_hash = hash('sha256', $raw_token);
    $stmt = mysqli_prepare($conn,
        "SELECT user_id FROM remember_tokens
         WHERE token_hash = ? AND expires_at > NOW()");
    mysqli_stmt_bind_param($stmt, 's', $token_hash);
    mysqli_stmt_execute($stmt);
    $res = mysqli_stmt_get_result($stmt);
    if ($row = mysqli_fetch_assoc($res)) {
        $_SESSION['user_id']      = $row['user_id'];
        $_SESSION['last_activity'] = time();
        header('Location: ../admin/dashboard.php'); exit();
    }
    // Cookie exists but token is invalid or expired — clear it
    setcookie('remember_me', '', time() - 3600, '/');
}

// ─── Process login form submission ────────────────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $username = trim($_POST['username'] ?? '');
    $password = $_POST['password'] ?? '';

    if ($username === '' || $password === '') {
        $error = 'Please enter both username and password.';
    } else {
        // Find user by username OR email — prepared statement (SQL-injection safe)
        $stmt = mysqli_prepare($conn,
            "SELECT id, password_hash, full_name, role
             FROM users WHERE username = ? OR email = ? LIMIT 1");
        mysqli_stmt_bind_param($stmt, 'ss', $username, $username);
        mysqli_stmt_execute($stmt);
        $user = mysqli_fetch_assoc(mysqli_stmt_get_result($stmt));

        if ($user && password_verify($password, $user['password_hash'])) {
            // Successful login — regenerate session ID to prevent fixation
            session_regenerate_id(true);
            $_SESSION['user_id']      = $user['id'];
            $_SESSION['user_name']    = $user['full_name'];
            $_SESSION['user_role']    = $user['role'];
            $_SESSION['last_activity'] = time();

            // If "Remember Me" was checked, create a secure cookie token
            if (isset($_POST['remember_me'])) {
                $raw_token  = bin2hex(random_bytes(32)); // 64-char random token
                $token_hash = hash('sha256', $raw_token);
                $expires_at = date('Y-m-d H:i:s', time() + ($REMEMBER_DAYS * 86400));
                $stmt2 = mysqli_prepare($conn,
                    "INSERT INTO remember_tokens (user_id, token_hash, expires_at)
                     VALUES (?, ?, ?)");
                mysqli_stmt_bind_param($stmt2, 'iss',
                    $user['id'], $token_hash, $expires_at);
                mysqli_stmt_execute($stmt2);
                // Set cookie: raw token (not hash) — httponly and samesite flags for security
                setcookie('remember_me', $raw_token,
                    time() + ($REMEMBER_DAYS * 86400),
                    '/', '', false, true // httpOnly=true prevents JS access
                );
            }
            header('Location: ../admin/dashboard.php'); exit();
        } else {
            // Use generic message — don't reveal whether username or password was wrong
            $error = 'Invalid username or password.';
        }
    }
}
?>
💡 Why use random_bytes(32) + SHA-256? random_bytes(32) generates 32 bytes of cryptographically secure random data. Converting it with bin2hex() gives a 64-character random string — essentially impossible to guess. Storing the SHA-256 hash in the database (not the raw token) means that even if your database is breached, an attacker cannot use the stored hashes to log in — they need the original raw token that only exists in the user’s browser cookie.

Step 4 — The Session Timeout + Auth Check File

Create auth/auth_check.php. Include this file at the top of every page that requires login. It checks: (1) is the user logged in via session? (2) has the session been idle for too long? If either check fails, it redirects to the login page.

PHP auth/auth_check.php — include at top of every protected page

<?php
if (session_status() === PHP_SESSION_NONE) { session_start(); }

$SESSION_TIMEOUT = 1800; // 30 minutes — change to 3600 for 1 hour

// ── Check 1: Is the user logged in at all? ─────────────────────────
if (!isset($_SESSION['user_id'])) {
    header('Location: /auth/login.php'); exit();
}

// ── Check 2: Has the session been idle too long? ────────────────────
if (isset($_SESSION['last_activity'])
    && (time() - $_SESSION['last_activity']) > $SESSION_TIMEOUT) {
    // Session has expired due to inactivity
    session_unset();
    session_destroy();
    // Redirect with a message parameter so login.php can show "Session expired"
    header('Location: /auth/login.php?expired=1'); exit();
}

// ── Update last activity timestamp on every valid page load ────────
$_SESSION['last_activity'] = time();

// Optional: Make user info available on every protected page
// e.g. echo "Welcome " . $_SESSION['user_name'];

Show “Session Expired” message on the login page

Add this snippet to auth/login.php just before the error display section:

PHP Add to login.php — show a message when the session expired

// Add this before the POST processing block in login.php:
if (isset($_GET['expired']) && $_GET['expired'] == '1') {
    $error = 'Your session expired due to inactivity. Please log in again.';
}

Step 5 — The Logout File

Create auth/logout.php. This destroys the session AND deletes the Remember Me cookie and its database record so the user cannot be auto-logged back in.

PHP auth/logout.php — full session + cookie cleanup

<?php
session_start();
require_once '../config/dbconnection.php';

// Delete the Remember Me token from database if cookie exists
if (isset($_COOKIE['remember_me']) && isset($_SESSION['user_id'])) {
    $token_hash = hash('sha256', $_COOKIE['remember_me']);
    $uid = (int)$_SESSION['user_id'];
    $stmt = mysqli_prepare($conn,
        "DELETE FROM remember_tokens WHERE user_id = ? AND token_hash = ?");
    mysqli_stmt_bind_param($stmt, 'is', $uid, $token_hash);
    mysqli_stmt_execute($stmt);
    // Delete the cookie from the browser
    setcookie('remember_me', '', time() - 3600, '/');
}

// Destroy the session
$_SESSION = [];
session_destroy();

header('Location: /auth/login.php'); exit();

Add logout link to your navigation

HTML Logout button for your navigation bar

<a href="/auth/logout.php" class="btn btn-outline-secondary btn-sm">
  🔓 Log Out
</a>

Step 6 — Add Auth Check to Every Protected Page

Add just this one line to the very top of every PHP page that requires login. No other changes needed to your existing pages.

PHP Add to the very top of admin/dashboard.php (and every other protected page)

<?php
require_once '../auth/auth_check.php';
// ... rest of your existing dashboard code unchanged ...
✅ That is everything. Your PHP project now has: (1) Secure login with password_verify(). (2) “Remember Me” that stores a cryptographic token — not your user ID. (3) Automatic 30-minute idle session timeout. (4) Proper logout that clears both session and cookie. (5) Session ID regeneration on login to prevent session fixation attacks.

Security Implementation Checklist

🛡 Is Your Login System Secure? — Tick Each Item

Tick items above to check your security score

Frequently Asked Questions

Why can’t I just store the user ID in the Remember Me cookie?

Because user IDs are predictable — they are sequential integers like 1, 2, 3. If an attacker sets their cookie to remember_user=1, they log in as user 1 (almost certainly the admin). The token approach stores a 64-character random hexadecimal string in the cookie. The only way to log in is to have the exact token — and with 2^256 possible tokens, guessing is computationally impossible.

What happens if a user has multiple devices with Remember Me active?

Each device gets its own unique token stored as a separate row in the remember_tokens table. When the user logs out on one device, only that device’s token is deleted (because we match on both user_id AND token_hash). The other devices remain logged in. If you want “logout from all devices,” change the logout query to DELETE FROM remember_tokens WHERE user_id = ? (without the token condition).

How do I change the session timeout from 30 minutes to something else?

Change the $SESSION_TIMEOUT = 1800 value in auth_check.php. The value is in seconds: 900 = 15 minutes, 1800 = 30 minutes, 3600 = 1 hour, 86400 = 24 hours. For admin areas of sensitive systems, 15–30 minutes is the industry standard. For a public-facing customer portal where the session timeout would be annoying, 1–2 hours is reasonable.

The Remember Me checkbox is ticked but I keep getting logged out when I close the browser. Why?

Two possible causes: (1) The cookie’s expiry time is set in the past or to zero — verify the setcookie() call includes time() + ($REMEMBER_DAYS * 86400) as the third argument. A value of 0 makes the cookie session-only (deleted when browser closes). (2) Your browser is configured to delete all cookies on close — this is a browser security setting that overrides cookie expiry dates. Test in a browser where this setting is off.


Related Tutorials on Codezips

How to Send Emails with PHPMailer + Gmail SMTP →

Send login notification emails to users

How to Build a REST API in PHP for Beginners →

Extend your login system with token-based API auth

Download PHP Management System Projects →

Add this login system to any downloaded project

How to Use AI to Debug Your PHP Code →

Debug login errors faster with the right AI prompts

Last updated April 2026. Tested on PHP 8.2 / MySQL 8.0 / XAMPP. Security recommendations based on OWASP Authentication Cheat Sheet 2026.

Leave a Comment

Your email address will not be published. Required fields are marked *

Scroll to Top