Authentication & Login System
Learn the general flow behind login systems and build a simplified registration, login, and page-protection example in PHP.
On this page 12 sections
Introduction
Almost every real web application needs some way to know who is using it. Authentication is the process of confirming that a visitor is who they claim to be, usually by checking a username or email against a password. Once that check succeeds, the application needs a way to "remember" the visitor as they move from page to page, which is where sessions come in.
In this lesson you will walk through the general shape of a login system: how a user registers, how the stored credentials get checked during login, and how a session keeps that user recognized across requests. You have already learned PDO and prepared statements, so the database side of these examples should feel familiar.
- The overall flow connecting registration, login, and sessions.
- How a registration form stores a user with a hashed password.
- How a login script checks submitted credentials against that stored hash.
- How to set $_SESSION['user_id'] on a successful login.
- How to protect a page by checking whether a session exists.
How a Login System Works
A typical login system is built from three cooperating pieces: a registration form that creates accounts, a login form that checks credentials, and a session mechanism that keeps the user "logged in" across multiple page loads. HTTP itself is stateless — every request is independent — so without a session, the server would forget who you are the instant you clicked a new link.
Passwords are never stored as plain text. Instead, a one-way hash of the password is stored, so that even if the database is ever exposed, the original passwords are not directly readable. The next lesson covers hashing in depth — here, we simply use it as part of the flow.
The Registration Flow
Registration is the step where an account is created. The form collects a username (or email) and a password, the server hashes that password, and both the username and the hash get saved as a new row in a users table.
<?php// Assume $pdo is an existing PDO connection (see the PDO lesson)
$username = $_POST['username'];$plainPassword = $_POST['password'];
// password_hash() is covered in detail in the next lesson.// For now, just know it turns a plain password into a secure, one-way hash.$hashedPassword = password_hash($plainPassword, PASSWORD_DEFAULT);
$stmt = $pdo->prepare('INSERT INTO users (username, password) VALUES (:username, :password)');$stmt->execute([ 'username' => $username, 'password' => $hashedPassword,]);
echo 'Account created. You can now log in.';The database only ever receives $hashedPassword — the raw $plainPassword is used for one calculation and then discarded. It is never written to the database, a log file, or anywhere else.
The Login Flow
Login reverses the lookup: given a username and a submitted password, the script fetches the stored hash for that username and checks whether the submitted password matches it. It never re-hashes and compares hashes directly — instead it uses a dedicated verification function (password_verify(), covered next lesson) that understands how the hash was built.
Example: A Simplified Login Script
Here is a simplified version of a login script that ties the pieces together. It fetches the user by username, verifies the password, and — on success — starts a session and stores the logged-in user's id.
<?phpsession_start();
// Assume $pdo is an existing PDO connection$username = $_POST['username'];$submittedPassword = $_POST['password'];
$stmt = $pdo->prepare('SELECT id, username, password FROM users WHERE username = :username');$stmt->execute(['username' => $username]);$user = $stmt->fetch();
if ($user && password_verify($submittedPassword, $user['password'])) { // Credentials are correct — start tracking this user in the session $_SESSION['user_id'] = $user['id'];
header('Location: dashboard.php'); exit;} else { echo 'Invalid username or password.';}Click Run to see what this code prints.
session_start() must run before any output is sent and before $_SESSION is read or written. It is usually the very first line of any script that needs session data, right after the opening <?php tag.
Protecting Pages with Sessions
Once a user is logged in, other pages need to check that a valid session exists before showing protected content. The pattern is simple: start the session, check whether the expected session value is set, and redirect away (usually back to the login page) if it is not.
<?phpsession_start();
if (!isset($_SESSION['user_id'])) { // No logged-in user in this session — send them to log in first header('Location: login.php'); exit;}
// From here on, we know a logged-in user is viewing the pageecho 'Welcome back, user #' . $_SESSION['user_id'];Because this check is just a few lines, it is common to place it at the top of every page that requires a login, or to pull it into its own file and require it at the top of each protected script.
<?phpsession_start();
$_SESSION = [];session_destroy();
header('Location: login.php');exit;A Note on Production Security
The scripts above show the core flow, but a real system needs a lot more: input validation, rate limiting on login attempts, CSRF protection on forms, secure session cookie settings (HttpOnly, Secure, SameSite), password reset flows, and often two-factor authentication. Lesson 68 (Security Best Practices) expands on hardening an application like this. For real projects, frameworks like Laravel or Symfony provide battle-tested authentication out of the box — reimplementing it from scratch in production is rarely a good idea.
Common Mistakes
- Forgetting session_start() at the top of every script that reads or writes $_SESSION.
- Storing the plain-text password anywhere, even temporarily, in logs or session data.
- Checking isset($_SESSION['user_id']) after output has already been sent, or in the wrong order relative to session_start().
- Trusting a login form without validating and sanitizing the submitted username.
- Assuming this simplified example is safe to deploy as-is.
Best Practices
- Always call session_start() before touching $_SESSION or producing any output.
- Store only the minimum needed in a session, such as a user id — not the full user record or password.
- Redirect immediately (and call exit) after header('Location: ...') so the rest of the script never runs.
- Use prepared statements (PDO) for every query involving user input, as covered in earlier lessons.
- Treat this lesson as a foundation, and layer in real security practices before using authentication in production.
Frequently Asked Questions
PHP sessions are per-request. session_start() tells PHP to resume the existing session (based on the visitor's cookie) so $_SESSION is populated with whatever was stored earlier, such as user_id.
It is enough to confirm someone is logged in, but a complete application also needs authorization checks (is this user allowed to see this specific data), CSRF protection, and other safeguards.
The rest of the script keeps running even though a redirect was sent, which can leak content or perform actions that should only happen for authenticated users. Always follow a redirect with exit.
Technically yes, but it is best practice to store as little as possible (usually just the id) and re-fetch other user details from the database when needed, so the session data never goes stale.
Key Takeaways
- A login system connects three steps: registration, login verification, and session tracking.
- Registration stores a hashed password, never the plain-text password.
- Login compares a submitted password against the stored hash and, on success, starts a session.
- Storing $_SESSION['user_id'] lets later pages recognize the same logged-in user.
- Protecting a page means checking for a valid session and redirecting unauthenticated visitors away.
- This lesson's scripts are simplified for learning — production systems need far more hardening.
Summary
You now understand the overall anatomy of a login system: a registration form that stores a hashed password, a login script that verifies credentials and starts a session, and protected pages that check for that session before rendering.
The next lesson zooms into the hashing functions used here — password_hash() and password_verify() — and explains exactly how they keep stored passwords safe.
- You understand the registration → login → session flow.
- You can write a simplified login script that checks a hashed password.
- You can protect a page by checking for an active session.
- You know this example is a teaching foundation, not a production-ready system.