LearnContact
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
Location: dashboard.php
(The browser is redirected, and $_SESSION['user_id'] now holds the logged-in user's id for every later request.)
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

Why do I need to call session_start() on every protected page?

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.

Is checking $_SESSION['user_id'] enough to secure a page?

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.

What happens if I skip exit after a header('Location: ...') redirect?

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.

Can I store more than the user id in the session?

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