A login system is part of almost every PHP project, and getting it right means understanding two things: sessions (how PHP remembers a logged-in user across pages) and password hashing (how you store passwords safely). This guide builds a complete, secure login system step by step, registration, login, protecting pages, and logout, with working code and the security done properly.
password_hash and password_verify functions. If you are new to connecting PHP to a database, read the PDO connection basics first, then come back, this guide assumes you can already connect to MySQL.
- How a login system actually works
- The users table
- Registration (with password hashing)
- Login (verifying the password and starting a session)
- Protecting a page (checking the session)
- Logout (destroying the session)
- Security essentials
1. How a login system actually works
A login system has a simple flow once you see it. When a user registers, you store their details with a securely hashed password, never the plain password. When they log in, you look up their record, verify the typed password against the stored hash, and if it matches, you start a session that remembers them. Every protected page then checks that session before showing anything. When they log out, you destroy the session. That is the entire system: hash on register, verify on login, remember with a session, check on every protected page, destroy on logout.
2. The users table
Start with a simple table to hold users:
CREATE TABLE users (
id INT AUTO_INCREMENT PRIMARY KEY,
username VARCHAR(50) UNIQUE NOT NULL,
password VARCHAR(255) NOT NULL
);
Note the password column is VARCHAR(255). This matters: a hashed password is much longer than the original, and 255 characters leaves room for it. Making this column too short is a common beginner mistake that quietly breaks logins.
3. Registration (with password hashing)
register.php
When a user registers, hash the password with password_hash before storing it. This turns the password into a secure, irreversible hash.
// assume $pdo is your PDO connection $username = $_POST['username']; $password = $_POST['password']; // hash the password (never store the plain text) $hash = password_hash($password, PASSWORD_DEFAULT); try { $stmt = $pdo->prepare( "INSERT INTO users (username, password) VALUES (?, ?)" ); $stmt->execute([$username, $hash]); echo "Registration successful. You can now log in."; } catch (PDOException $e) { // likely a duplicate username (UNIQUE constraint) echo "That username is already taken."; }
password_hash with PASSWORD_DEFAULT automatically uses a strong, modern hashing algorithm and handles the technical details (like salting) for you. You never need to write your own password encryption, this is the correct, secure way.
4. Login (verifying the password and starting a session)
login.php
On login, look up the user by username, then use password_verify to check the typed password against the stored hash. If it matches, start a session and store something that marks the user as logged in.
session_start(); $username = $_POST['username']; $password = $_POST['password']; $stmt = $pdo->prepare("SELECT * FROM users WHERE username = ?"); $stmt->execute([$username]); $user = $stmt->fetch(); if ($user && password_verify($password, $user['password'])) { // correct password: log them in $_SESSION['user_id'] = $user['id']; $_SESSION['username'] = $user['username']; header("Location: dashboard.php"); exit; } else { echo "Invalid username or password."; }
5. Protecting a page (checking the session)
dashboard.php
Any page that should only be visible to logged-in users must check the session at the very top, before outputting anything. If there is no logged-in user, send them to the login page.
session_start(); // if not logged in, redirect to login if (!isset($_SESSION['user_id'])) { header("Location: login.php"); exit; } // safe to show protected content echo "Welcome, " . htmlspecialchars($_SESSION['username']);
This check is what actually protects your pages. Put it at the top of every page that requires login. Notice htmlspecialchars when printing the username, that prevents anyone’s stored name from injecting HTML or scripts into your page.
6. Logout (destroying the session)
logout.php
Logging out means ending the session completely:
session_start(); session_unset(); // clear session variables session_destroy(); // destroy the session header("Location: login.php"); exit;
7. Security essentials
A login system is a security feature, so these are not optional extras, they are the point:
- Always hash passwords with
password_hash, and verify withpassword_verify. Never store or compare plain text passwords. - Always use prepared statements (as shown) so login fields cannot be used for SQL injection.
- Use one generic login error so you do not reveal which usernames exist.
- Escape output with
htmlspecialcharswhen displaying user-provided data. - Check the session at the top of every protected page, before any output.
Frequently asked questions
Why use sessions for login?
HTTP is stateless, meaning each page request is independent and the server does not naturally remember you between pages. A session stores a small piece of information on the server that identifies the logged-in user across requests, which is what keeps you logged in as you move around the site.
Is password_hash secure enough?
Yes. password_hash with PASSWORD_DEFAULT uses a strong, modern, salted hashing algorithm and is the officially recommended way to handle passwords in PHP. You should not write your own password encryption.
Why is my session not working?
The most common cause is forgetting to call session_start() at the very top of every page that uses the session, before any HTML or output. It must run before anything is sent to the browser.
Why must the password column be VARCHAR(255)?
A hashed password is much longer than the original text. If the column is too short, the hash gets cut off when stored, and logins silently fail. 255 characters gives plenty of room.

