Aim
To design a login form and validate it with PHP — server-side field checks plus hashed password verification using password_hash() / password_verify().
Theory
Validation must happen on the server. HTML attributes like required and JavaScript checks only improve the user experience; anyone can bypass them with DevTools or curl. The server therefore re-checks everything: trim() strips accidental whitespace and empty() catches missing or blank fields before any credential work begins.
Passwords are never stored or compared as plain text. password_hash($pwd, PASSWORD_DEFAULT) produces a salted bcrypt string (~60 characters) that encodes the algorithm, cost, random salt and digest together. Because the salt is random, hashing the same password twice gives different strings — so equality tests on hashes are useless; password_verify($input, $hash) extracts the salt, re-hashes the input and compares safely. Practical 10 compared plain strings; this is the production upgrade.
The validation ladder runs cheapest-first: empty fields → unknown username → wrong password → success. One trade-off: the distinct messages User '…' not found. and Incorrect password. are instructive in a lab but let an attacker enumerate valid usernames — production systems reply with one generic "invalid credentials" message.
Requirements
- XAMPP/WAMP with Apache and PHP 8.x
- Code editor (VS Code)
- Browser (Chrome/Edge) — the form must be POSTed, so run it under Apache, not the CLI
Procedure
- Start Apache from the XAMPP Control Panel.
- Save the snippet as
p15_login_validation.phpinC:\xampp\htdocs\wbplab. - Open
http://localhost/wbplab/p15_login_validation.php— the login form appears. - Try an unknown username, then
rohitwith a wrong password, thenrohit/rohit@123— each stage of the ladder produces its own message. - Click Logout on the success panel to return to the form.
Explanation of the Code
$usersmaps usernames to freshly computedpassword_hash()values — two demo accounts,rohitandstudent. (Real apps hash once, at registration, and store the result in a database.)- The handler runs only when
$_SERVER["REQUEST_METHOD"] === "POST"and theloginbutton is present in$_POST. trim($_POST["username"] ?? "")reads each field safely;??supplies a fallback when the key is missing.- The ladder:
empty()on either field →All fields are required!;!isset($users[$username])→User '…' not found.;!password_verify($password, $users[$username])→Incorrect password.; otherwise$success = trueand the name is kept in$loggedUser. $dashDisplay/$loginDisplayternaries flip the success panel and the form — the same self-posting pattern as Practical 10.$erroris echoed throughhtmlspecialchars(), essential here because the "not found" message embeds the typed username.- The Logout button only calls the client-side
doLogout()display toggle; genuine session logout is Practical 16's subject.
Expected Output
First load shows the teal Login form with the hint rohit / rohit@123. Submitting admin with any password gives the red message User 'admin' not found.; rohit with a wrong password gives Incorrect password.; blank fields (with required bypassed) give All fields are required!. The pair rohit / rohit@123 replaces the form with a green panel — ✓ Login Validated, Welcome, rohit! and a note that the password was checked with password_verify() — whose red Logout button flips back to the form with ✓ Logged out successfully.
🎯 Viva Questions
- The inputs have
required— why validate in PHP at all? Client-side checks are advisory; requests can be forged without the browser, so only server-side validation is authoritative. - What does
password_hash()return? A self-contained string (~60 chars for bcrypt) encoding algorithm, cost, random salt and digest — everythingpassword_verify()later needs. - Why not compare
password_hash($input) === $storedHash? Each call uses a fresh random salt, so the same password never hashes to the same string; onlypassword_verify()can match them. - Why are separate "user not found" / "incorrect password" messages risky in production? They confirm which usernames exist, enabling user enumeration; production returns one generic failure message.
- What do
trim()and the??operator contribute?trim()removes stray whitespace so" rohit "still matches;??avoids undefined-index warnings when a key is absent. - Where would the
$usersarray live in a real application? In a database table storing hashes created at registration, with the logged-in state then carried by a session rather than a page-local variable.
CO Mapping
CO1, CO2