16. Live Project: Building a Reels App with EC2, S3 and RDS
16.6 Accounts: Register, Login and Logout
Tin chote pages. Passwords password_hash() ne hash hotat, login nantar session_regenerate_id(true), aani logout fakt POST + CSRF ne.
public/register.php
<?php
// public/register.php – create an account (password_hash, prepared statements)
require __DIR__ . '/../src/bootstrap.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
csrf_check();
$username = trim((string) ($_POST['username'] ?? ''));
$password = (string) ($_POST['password'] ?? '');
if (!preg_match('/^[A-Za-z0-9_]{3,30}$/', $username)) {
$error = 'Username: 3-30 letters, numbers or _';
} elseif (strlen($password) < 8) {
$error = 'Password must be at least 8 characters';
} else {
try {
$stmt = db()->prepare('INSERT INTO users (username, password_hash) VALUES (?, ?)');
$stmt->execute([$username, password_hash($password, PASSWORD_DEFAULT)]);
session_regenerate_id(true);
$_SESSION['uid'] = (int) db()->lastInsertId();
$_SESSION['uname'] = $username;
header('Location: /');
exit;
} catch (PDOException $ex) {
if (($ex->errorInfo[1] ?? 0) === 1062) { // duplicate key on UNIQUE(username)
$error = 'Username already taken';
} else {
error_log('register: ' . $ex->getMessage());
$error = 'Could not register';
}
}
}
}
page_top('Create account');
if ($error !== '') {
echo '<p class="err">' . e($error) . '</p>';
}
?>
<form method="post" autocomplete="off">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<label>Username <input name="username" required maxlength="30" pattern="[A-Za-z0-9_]{3,30}"></label>
<label>Password <input name="password" type="password" required minlength="8"></label>
<button type="submit">Register</button>
</form>
<p>Already have an account? <a href="/login.php">Log in</a></p>
<?php page_bottom();
public/login.php
<?php
// public/login.php – log in with password_verify and a fresh session ID
require __DIR__ . '/../src/bootstrap.php';
$error = '';
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
csrf_check();
$username = trim((string) ($_POST['username'] ?? ''));
$password = (string) ($_POST['password'] ?? '');
$stmt = db()->prepare('SELECT id, username, password_hash FROM users WHERE username = ?');
$stmt->execute([$username]);
$user = $stmt->fetch();
if ($user && password_verify($password, $user['password_hash'])) {
session_regenerate_id(true); // prevents session fixation
$_SESSION['uid'] = (int) $user['id'];
$_SESSION['uname'] = $user['username'];
header('Location: /');
exit;
}
usleep(300000); // slow down password guessing a little
$error = 'Wrong username or password'; // same message for both cases
}
page_top('Log in');
if ($error !== '') {
echo '<p class="err">' . e($error) . '</p>';
}
?>
<form method="post">
<input type="hidden" name="csrf" value="<?= e(csrf_token()) ?>">
<label>Username <input name="username" required maxlength="30"></label>
<label>Password <input name="password" type="password" required></label>
<button type="submit">Log in</button>
</form>
<p>New here? <a href="/register.php">Create an account</a></p>
<?php page_bottom();
public/logout.php
<?php
// public/logout.php – POST only, with CSRF check
require __DIR__ . '/../src/bootstrap.php';
if ($_SERVER['REQUEST_METHOD'] !== 'POST') {
http_response_code(405);
exit('Method not allowed');
}
csrf_check();
$_SESSION = [];
session_destroy();
setcookie(session_name(), '', ['expires' => time() - 3600, 'path' => '/',
'secure' => cfg('cookie_secure'), 'httponly' => true, 'samesite' => 'Lax']);
header('Location: /login.php');
Why this matters for security
password_hash() uses a slow, salted algorithm, so a stolen users table cannot be reversed easily. One generic message ("Wrong username or password") stops username enumeration (वापरकर्ता नाव शोधणे). Regenerating the session ID on login defeats session fixation, and a POST-only logout with CSRF stops other sites from logging your users out.
Ravindra Bagale's Tip
When login fails, students show different messages like "User not found" and "Wrong password" – so an attacker learns which usernames exist! Always show a single message. And never use md5() or sha1() for passwords – only password_hash() / password_verify().
Ravindra Bagale's Tip – मराठी
Login fail झालं तर "User not found" आणि "Wrong password" असे वेगळे messages students देतात – attacker ला कोणते usernames आहेत ते कळतं! नेहमी एकच message द्या. आणि md5() किंवा sha1() passwords साठी कधी नाही – फक्त password_hash() / password_verify().
Ravindra Bagale's Tip – हिंदी
Login fail होने पर students "User not found" और "Wrong password" जैसे अलग-अलग messages देते हैं – attacker को पता चल जाता है कि कौन से usernames मौजूद हैं! हमेशा एक ही message दो. और passwords के लिए md5() या sha1() कभी नहीं – सिर्फ़ password_hash() / password_verify().
Lab
Register two users, log in and out, and look at the users table – confirm that password_hash starts with $2y$. Try logging in with a wrong password and a non-existent user and confirm the message is identical.