Login with sessions

password_hash and verify

Passwords are never stored as plain text. A hash is a one-way fingerprint: easy to compute, practically impossible to reverse. PHP computes it with password_hash() and checks a login attempt with password_verify() - the function compares, never you.

The second argument is the algorithm. PASSWORD_DEFAULT tracks the current best choice, so hashes stay strong without code changes. Store the full result in one text column - it already carries the algorithm, the cost, and the salt.

$hash = password_hash($password, PASSWORD_DEFAULT); if (password_verify($password, $hash)) { echo "Welcome back"; }

A login sketch

A login script does four things in order: start the session, load the user row by name, verify the password, and only then mark the session as logged in. Anything else - greeting the user, loading settings - waits until after the check.

On success, rotate the session id with session_regenerate_id(true) so an id seen before login cannot be reused after it. Then store the user id and redirect. The full login flow is covered in Authentication and Authorization in PHP 8.

session_start(); if (password_verify($password, $row["hash"])) { session_regenerate_id(true); $_SESSION["user_id"] = (int) $row["id"]; header("Location: /dashboard.php"); exit; }

Guard pages

Every protected page repeats the same two lines: start the session, then demand a logged-in marker. Put the check at the very top, before any output - headers and redirects stop working once the page starts printing.

session_start(); if (!isset($_SESSION["user_id"])) { header("Location: /login.php"); exit; }

Logout

Logout reverses login: empty the session array, destroy the session on the server, and send the user back to the login page. Destroying without emptying, or emptying without destroying, leaves half a session behind - which is how ghost logins happen.

session_start(); $_SESSION = []; session_destroy(); header("Location: /login.php"); exit;

Next: JSON REST endpoint

Article author: Arthur Isaev