PHPLogin & Authentication Basics

Login & Authentication Basics

Authentication is the process of confirming that a visitor really is who they claim to be, usually by checking a password against one stored for that account. In plain PHP this comes down to a handful of pieces working together: a login form, a lookup against stored user data, a password comparison that never touches a plaintext password, a session that remembers the result, and a way to protect pages so only logged-in visitors can reach them. This page walks through a complete, minimal version of that flow.

Never store or compare plaintext passwords

A user's real password should never be saved anywhere, including in your database. Instead, PHP's password_hash() turns a password into a salted, one-way hash at signup time, and password_verify() checks a login attempt against that hash without ever needing the original password back. Both functions currently use the bcrypt algorithm by default and handle the salt automatically, so there is no separate salt to generate, store, or lose track of.

Hashing a password at signup time

PHP
<?php
$plainPassword = $_POST['password']; // from the signup form
$hash = password_hash($plainPassword, PASSWORD_DEFAULT);

// $hash is what gets saved in the database — never $plainPassword
// e.g. INSERT INTO users (email, password_hash) VALUES (?, ?)
Do not write your own hashing or encryption for passwords
`md5()`, `sha1()`, and even plain `hash('sha256', ...)` are the wrong tool here — they are fast general-purpose hashes designed for checksums, which makes them fast for an attacker to brute-force too. `password_hash()` is deliberately slow and salted, and it is maintained by the PHP core team, so it already reflects current best practice. There is no good reason to build a substitute.
The login form

A simple login form

HTML
<form method="post" action="/login.php">
  <label for="email">Email</label>
  <input type="email" id="email" name="email" required>

  <label for="password">Password</label>
  <input type="password" id="password" name="password" required>

  <button type="submit">Log in</button>
</form>
Verifying the credentials

The login script looks the account up by email (never by trusting an id or role sent from the client), then hands the submitted password and the stored hash to password_verify(). Only if that returns true is the visitor considered authenticated. The database lookup below is mocked for clarity — in a real application it would be a parameterized query against your users table.

login.php

PHP
<?php
session_start();

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $email = trim($_POST['email'] ?? '');
    $password = $_POST['password'] ?? '';

    // Mocked DB lookup — in real code this is a prepared statement, e.g.
    // SELECT id, email, password_hash FROM users WHERE email = ?
    $user = findUserByEmail($email);

    if ($user !== null && password_verify($password, $user['password_hash'])) {
        // Correct credentials: rotate the session id before trusting it
        session_regenerate_id(true);

        $_SESSION['user_id'] = $user['id'];
        $_SESSION['email'] = $user['email'];

        header('Location: /dashboard.php');
        exit;
    }

    $error = 'Invalid email or password.';
}
Give the same error for a wrong email or a wrong password
Reporting "no account with that email" separately from "wrong password" tells an attacker which emails are registered at all. A single generic message like "Invalid email or password" avoids leaking that information.
Why session_regenerate_id() runs right after login

This is the same defense covered on the session security page: calling session_regenerate_id(true) the moment a visitor proves who they are prevents session fixation, where an attacker could otherwise have handed the victim a known session id before login and then reused that same id afterward. It costs one function call and closes off an entire class of attack.

Guarding protected pages

Every page that should only be visible to logged-in visitors needs to check for a valid session before rendering anything sensitive. A small reusable guard function keeps that check consistent across the whole application instead of it being copied, and possibly forgotten, on individual pages.

A reusable requireLogin() guard

PHP
<?php
function requireLogin(): void
{
    if (empty($_SESSION['user_id'])) {
        header('Location: /login.php');
        exit;
    }
}

dashboard.php

PHP
<?php
session_start();
require_once 'guards.php';

requireLogin(); // exits early if not logged in

echo 'Welcome back, ' . htmlspecialchars($_SESSION['email'], ENT_QUOTES, 'UTF-8');
Welcome back, aisha@example.com
Logging out

Logging out should undo everything the login flow set up: clear the session data, destroy the server-side session, and remove the session cookie so a copied or reused cookie value cannot be replayed afterward.

logout.php

PHP
<?php
session_start();

$_SESSION = [];
session_unset();
session_destroy();

if (ini_get('session.use_cookies')) {
    $params = session_get_cookie_params();
    setcookie(
        session_name(),
        '',
        time() - 42000,
        $params['path'],
        $params['domain'],
        $params['secure'],
        $params['httponly']
    );
}

header('Location: /login.php');
exit;
Putting the whole flow together
  • Store only a password_hash() output at signup, never the plaintext password.

  • On login, look the user up server-side and check their password with password_verify().

  • Call session_regenerate_id(true) immediately after a successful login.

  • Store only an internal identifier (like user_id) in $_SESSION, not sensitive data.

  • Protect every restricted page with a shared requireLogin()-style guard, called before any output.

  • On logout, clear $_SESSION, call session_destroy(), and expire the session cookie.

Tip
Resist the urge to build your own password hashing, custom token scheme, or "clever" encryption for credentials. `password_hash()` / `password_verify()` plus PHP's built-in session handling, used correctly, already covers the vast majority of what a typical application needs — the security work is in wiring them together correctly, not in inventing new cryptography.