Topic 7: Sessions, Login Page, Logout (Session Destroy, Timeout, Cookies Introduction, Page Redirect)
📖 5 min read · 🎯 intermediate · 🧭 Prerequisites: multi-processing-in-nodejs, react-lists
Why this matters
Up until now, your PHP pages have no memory — every request starts fresh, with no idea who's on the other side. That's fine for a blog post, but the moment you add a login page, you need the server to remember: "Yes, this person already signed in." That's exactly what sessions give you. In this lesson we'll build a real login flow in PHP — session start, session destroy on logout, automatic timeout when someone walks away, and a first look at cookies for returning users.
What You'll Learn
- Create a complete PHP login page that authenticates users against a MySQL database
- Start, read, and destroy PHP sessions to track who is logged in
- Implement a 30-minute session timeout for automatic logout
- Set and read cookies to remember returning users across browser restarts
- Redirect users with
header("Location: ...")based on authentication state
The Analogy
Think of your PHP application as a city museum with restricted galleries. The login page is the front desk — a visitor presents their ID (username and password), the clerk checks the registry (the database), and if everything matches, they hand over a wristband ($_SESSION). Every room the visitor enters checks for that wristband. If they've been idle for 30 minutes, a security guard quietly removes it and asks them to check back in. Cookies are the annual membership card tucked in a wallet: even after the visitor goes home and returns the next day, the front desk recognizes their face and fastens the wristband again without a full interrogation.
Chapter 1: Database Setup
Before writing a single line of PHP, you need a users table in MySQL. Store hashed passwords — never plain text.
CREATE DATABASE user_management;
USE user_management;
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) NOT NULL UNIQUE,
password VARCHAR(255) NOT NULL
);
INSERT INTO users (username, password) VALUES
('admin', '$2y$10$WzH/q9TvkhO92eB.k7B8ue4jeVRRTY7jbWnce7xxEr5EIKD1S/WPC');
-- plain-text equivalent: 'password'
-- generated with: password_hash('password', PASSWORD_BCRYPT)
The password column is 255 characters to accommodate the full bcrypt hash output. The UNIQUE constraint on username prevents duplicate accounts at the database level.
Chapter 2: Creating the Login Page
Create login.php. This file does two jobs: it renders the HTML form on a GET request and processes credentials on a POST request.
<?php
session_start();
$servername = "localhost";
$username = "root";
$password = "";
$dbname = "user_management";
$conn = new mysqli($servername, $username, $password, $dbname);
if ($conn->connect_error) {
die("Connection failed: " . $conn->connect_error);
}
if ($_SERVER["REQUEST_METHOD"] == "POST") {
$username = $_POST['username'];
$password = $_POST['password'];
$sql = "SELECT * FROM users WHERE username = '$username'";
$result = $conn->query($sql);
if ($result->num_rows > 0) {
$row = $result->fetch_assoc();
if (password_verify($password, $row['password'])) {
$_SESSION['username'] = $username;
header("Location: welcome.php");
exit();
} else {
echo "Invalid password.";
}
} else {
echo "No user found.";
}
}
?>
<!DOCTYPE html>
<html>
<head>
<title>Login</title>
</head>
<body>
<form method="post" action="">
Username: <input type="text" name="username" required><br>
Password: <input type="password" name="password" required><br>
<button type="submit">Login</button>
</form>
</body>
</html>
Key points:
session_start()must be called before any output — it initialises (or resumes) the session cookie exchange with the browser.password_verify($password, $row['password'])compares the submitted plain-text password against the stored bcrypt hash. Never compare plain text to plain text.header("Location: welcome.php")followed immediately byexit()performs the redirect and stops further execution. Theexit()is non-negotiable — without it, the script continues running after sending the header.
Chapter 3: Creating the Welcome Page
Create welcome.php. This page is the "restricted gallery" — only visitors wearing the session wristband may enter.
<?php
session_start();
if (!isset($_SESSION['username'])) {
header("Location: login.php");
exit();
}
echo "Welcome, " . $_SESSION['username'];
?>
<!DOCTYPE html>
<html>
<head>
<title>Welcome</title>
</head>
<body>
<p>You are logged in.</p>
<a href="logout.php">Logout</a>
</body>
</html>
The guard check — if (!isset($_SESSION['username'])) — redirects unauthenticated visitors back to the login page before they see any protected content.
Chapter 4: Creating the Logout Page
Create logout.php. Logging out means both clearing the session data and destroying the session itself.
<?php
session_start();
session_unset(); // clear all session variables
session_destroy(); // destroy the session on the server
header("Location: login.php");
exit();
?>
session_unset()empties the$_SESSIONsuperglobal array.session_destroy()deletes the session file (or DB row) on the server, invalidating the session ID entirely.- Both steps together ensure no residual data leaks.
Chapter 5: Session Timeout and Cookies
Session Timeout
Add a 30-minute inactivity timeout to both login.php (after authentication) and welcome.php (at the top of every page load). Place this block immediately after session_start():
<?php
// login.php and welcome.php — add this right after session_start()
session_start();
$timeout_duration = 1800; // 30 minutes in seconds
if (isset($_SESSION['LAST_ACTIVITY']) &&
(time() - $_SESSION['LAST_ACTIVITY']) > $timeout_duration) {
session_unset();
session_destroy();
header("Location: login.php");
exit();
}
$_SESSION['LAST_ACTIVITY'] = time(); // refresh timestamp on every page load
Every page load stamps the current Unix timestamp into $_SESSION['LAST_ACTIVITY']. On the next request, if more than 1800 seconds have elapsed since that stamp, the session is destroyed and the user is bounced back to the login page.
Using Cookies
Cookies persist across browser sessions (unlike the in-memory PHP session cookie). Use them to remember a returning user's identity so you can restore their session without forcing a full re-login.
In login.php — set the cookie on successful login:
<?php
// Inside the successful password_verify() branch in login.php
if (password_verify($password, $row['password'])) {
$_SESSION['username'] = $username;
// Set a cookie that expires in 30 days
setcookie("username", $username, time() + (86400 * 30), "/");
header("Location: welcome.php");
exit();
}
setcookie parameters:
"username"— cookie name$username— value stored in the cookietime() + (86400 * 30)— expiry: current time plus 30 days (86 400 seconds × 30)"/"— path scope: the cookie is sent for every URL on this domain
In welcome.php — restore session from cookie if session is absent:
<?php
session_start();
// Restore session from cookie if the session variable is missing
if (isset($_COOKIE['username']) && !isset($_SESSION['username'])) {
$_SESSION['username'] = $_COOKIE['username'];
}
if (!isset($_SESSION['username'])) {
header("Location: login.php");
exit();
}
This means a user who closes and reopens the browser (clearing the in-memory session) will have their session silently restored from the cookie during the next visit — no re-login needed until the 30-day cookie expires.
sequenceDiagram
participant Browser
participant login.php
participant DB
participant welcome.php
participant logout.php
Browser->>login.php: POST username + password
login.php->>DB: SELECT * FROM users WHERE username = ?
DB-->>login.php: Row with hashed password
login.php->>login.php: password_verify()
login.php->>Browser: Set-Cookie: username=admin (30d) + redirect to welcome.php
Browser->>welcome.php: GET (with session cookie + username cookie)
welcome.php->>welcome.php: Check $_SESSION['username']
welcome.php-->>Browser: 200 Welcome page
Browser->>logout.php: GET /logout.php
logout.php->>logout.php: session_unset() + session_destroy()
logout.php->>Browser: redirect to login.php
🧪 Try It Yourself
Task: Wire up the full login/logout flow locally.
- Create the
user_managementdatabase anduserstable using the SQL from Chapter 1. - Create
login.php,welcome.php, andlogout.phpfrom the examples above. - Start a local PHP server in the same directory:
php -S localhost:8000
- Open
http://localhost:8000/login.phpin your browser and log in withadmin/password.
Success criterion: After login you should land on http://localhost:8000/welcome.php and see "Welcome, admin". Clicking "Logout" should redirect you back to the login page and make welcome.php inaccessible (redirecting you back to login if you try to visit it directly).
Bonus: Change $timeout_duration to 10 (10 seconds), wait 11 seconds on welcome.php, then refresh — you should be redirected to login.php automatically.
🔍 Checkpoint Quiz
Q1. Why must session_start() be called before any HTML output in a PHP file?
Q2. Given this snippet, what happens when a user visits welcome.php without a valid session?
<?php
session_start();
if (!isset($_SESSION['username'])) {
header("Location: login.php");
exit();
}
echo "Welcome, " . $_SESSION['username'];
A) It shows "Welcome, " with an empty string
B) It redirects to login.php and stops execution
C) It throws a PHP fatal error
D) It shows a blank page
Q3. What is the difference between session_unset() and session_destroy()?
Q4. You want a "remember me" cookie to last exactly 7 days. Which setcookie call is correct?
A) setcookie("user", $name, time() + 604800, "/");
B) setcookie("user", $name, 604800, "/");
C) setcookie("user", $name, time() + 7, "/");
D) setcookie("user", $name, time() - 604800, "/");
A1. session_start() sends HTTP headers (specifically the Set-Cookie header for the session ID). HTTP headers must be sent before any body content — once the browser receives output, headers are already flushed and can no longer be modified.
A2. B — !isset($_SESSION['username']) is true, so header("Location: login.php") fires and exit() halts the script before echo is ever reached.
A3. session_unset() clears the contents of the $_SESSION array (variables gone, session still exists on the server). session_destroy() deletes the entire session record from the server, invalidating the session ID. For a clean logout you call both: unset the data first, then destroy the container.
A4. A — time() + 604800 is the correct Unix timestamp 7 days from now (7 × 24 × 60 × 60 = 604 800 seconds). Option B passes a raw offset instead of an absolute timestamp. Option C adds only 7 seconds. Option D subtracts time, immediately expiring the cookie.
🪞 Recap
- Call
session_start()at the top of every PHP file that reads or writes session data — before any output. - Use
password_hash()at registration andpassword_verify()at login; never store or compare plain-text passwords. - Protect pages by checking
$_SESSION['username']and redirecting withheader("Location: ...")+exit()when the check fails. - Implement session timeout by storing
$_SESSION['LAST_ACTIVITY']and comparing againsttime()on each page load. - Use
setcookie()to persist a "remember me" identity across browser restarts; restore the session from$_COOKIEinwelcome.phpwhen the session variable is missing.
📚 Further Reading
- PHP Sessions — php.net — the source of truth on every
session_*function and configuration directive - PHP setcookie — php.net — full parameter reference for cookie flags including
HttpOnlyandSecure - OWASP Session Management Cheat Sheet — production-grade hardening beyond the basics covered here
- ⬅️ Previous: React Lists
- ➡️ Next: Adding Middleware