LearnAI ToolsCareerPractice BuildsPlayContact
Lesson 6224 min read

Authentication & Login System

Learn the general flow behind login systems and build a simplified registration, login, and page-protection example in PHP.

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.

What You Will Learn
  • 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.

User registers with a username/email and password
↓
Server hashes the password and stores the user record
↓
User later submits the login form
↓
Server looks up the user and verifies the password hash
↓
On success, the server starts a session and stores the user's id in it
↓
The session id (via a cookie) lets every later request recognize the same user
Why Not Just Store the Password?

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.

register.php (simplified)
<?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.';
Notice What Gets Stored

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.

Look up the user row by username/email
↓
If no matching user exists, reject the login
↓
Verify the submitted password against the stored hash
↓
If verification fails, reject the login
↓
If verification succeeds, start a session and store the user's id

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.

login.php (simplified)
<?php
session_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.';
}
On a Successful Login

Click Run to see what this code prints.

Always Call session_start() First

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.

dashboard.php (protected page)
<?php
session_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 page
echo '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.

A logout script, for completeness
<?php
session_start();
$_SESSION = [];
session_destroy();
header('Location: login.php');
exit;

A Note on Production Security

This Is a Teaching Example, Not a Production Auth System

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

Avoid These 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.

Lesson 62 Completed
  • 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.
Next Lesson →

Password Hashing