Authentication System
Learn how user login and session management works
Lesson 4: Authentication System
Authentication verifies user identity and maintains login state across requests.
The Authentication Flow
Registration
User creates an account:
// Hash the password
$hashedPassword = password_hash($_POST['password'], PASSWORD_BCRYPT);
// Store in database
$db->query('INSERT INTO users (email, password) VALUES (:email, :password)', [
'email' => $_POST['email'],
'password' => $hashedPassword
]);Login
User submits credentials:
$auth = new Authenticator();
if ($auth->attempt($_POST['email'], $_POST['password'])) {
redirect('/dashboard');
} else {
$errors['email'] = 'Invalid credentials';
}Session Storage
User data stored in session:
$_SESSION['user'] = [
'email' => $user['email']
];
session_regenerate_id(true); // Security!Password Security
Never Store Plain Text!
// ❌ WRONG - Plain text
$db->query('INSERT INTO users (password) VALUES (:password)', [
'password' => $_POST['password']
]);
// ✅ CORRECT - Hashed
$db->query('INSERT INTO users (password) VALUES (:password)', [
'password' => password_hash($_POST['password'], PASSWORD_BCRYPT)
]);How password_hash() Works
$password = 'secret123';
$hash = password_hash($password, PASSWORD_BCRYPT);
// Result: $2y$10$abcdefghijklmnopqrstuvwxyz...
// Same password, different hash each time (random salt)
$hash2 = password_hash($password, PASSWORD_BCRYPT);
// Result: $2y$10$zyxwvutsrqponmlkjihgfedcba...Why different hashes? Each hash includes a random salt, making rainbow table attacks impossible.
How password_verify() Works
$password = 'secret123';
$hash = '$2y$10$abcdefghijklmnopqrstuvwxyz...';
password_verify($password, $hash); // true
password_verify('wrong', $hash); // falseNever use == for password comparison!
// ❌ WRONG
if ($password == $user['password']) { }
// ✅ CORRECT
if (password_verify($password, $user['password'])) { }The Authenticator Class
class Authenticator {
public function attempt($email, $password) {
$user = App::resolve(Database::class)->query(
'SELECT * FROM users WHERE email = :email',
['email' => $email]
)->find();
if ($user && password_verify($password, $user['password'])) {
$this->login($user);
return true;
}
return false;
}
public function login($user) {
$_SESSION['user'] = [
'email' => $user['email']
];
session_regenerate_id(true);
}
public function logout() {
Session::destroy();
}
}Session Management
Starting Sessions
// In public/index.php
session_name('DALT_SESSION');
session_start();Storing User Data
$_SESSION['user'] = [
'email' => 'user@example.com',
'name' => 'John Doe'
];Checking Authentication
if (isset($_SESSION['user'])) {
// User is logged in
$email = $_SESSION['user']['email'];
}Session Regeneration
Always regenerate session ID after login:
session_regenerate_id(true);Why? Prevents session fixation attacks.
Complete Login Example
Registration Form
<form method="POST" action="/register">
<?= csrf_field() ?>
<input type="email" name="email" required>
<input type="password" name="password" required>
<button type="submit">Register</button>
</form>Registration Controller
// Validate
$errors = [];
if (!Validator::email($_POST['email'])) {
$errors['email'] = 'Invalid email';
}
if (!Validator::string($_POST['password'], 8, 255)) {
$errors['password'] = 'Password must be at least 8 characters';
}
if (!empty($errors)) {
return view('auth/register.view.php', ['errors' => $errors]);
}
// Hash password
$hashedPassword = password_hash($_POST['password'], PASSWORD_BCRYPT);
// Store user
$db->query('INSERT INTO users (email, password) VALUES (:email, :password)', [
'email' => $_POST['email'],
'password' => $hashedPassword
]);
// Auto-login
$auth = new Authenticator();
$auth->login(['email' => $_POST['email']]);
redirect('/dashboard');Login Controller
$auth = new Authenticator();
if ($auth->attempt($_POST['email'], $_POST['password'])) {
redirect('/dashboard');
}
$errors['email'] = 'Invalid credentials';
return view('auth/login.view.php', ['errors' => $errors]);Logout Controller
$auth = new Authenticator();
$auth->logout();
redirect('/');Protecting Routes
Use auth middleware:
// Require login
$router->get('/dashboard', 'dashboard.php')->only('auth');
$router->get('/profile', 'profile.php')->only('auth');
// Guest only (redirect if logged in)
$router->get('/login', 'auth/login.php')->only('guest');
$router->get('/register', 'auth/register.php')->only('guest');Common Security Issues
Timing Attacks
// ❌ VULNERABLE
if ($token === $sessionToken) { }
// ✅ SECURE
if (hash_equals($sessionToken, $token)) { }hash_equals() takes constant time, preventing timing attacks.
Session Fixation
// ❌ VULNERABLE
$_SESSION['user'] = $user;
// ✅ SECURE
$_SESSION['user'] = $user;
session_regenerate_id(true);SQL Injection
// ❌ VULNERABLE
$query = "SELECT * FROM users WHERE email = '{$email}'";
// ✅ SECURE
$db->query('SELECT * FROM users WHERE email = :email', ['email' => $email]);Always use prepared statements!
Debugging Authentication
Check Session
dd($_SESSION);Verify Password Hash
$hash = password_hash('password123', PASSWORD_BCRYPT);
dd([
'hash' => $hash,
'verify' => password_verify('password123', $hash)
]);Test Login Flow
// In Authenticator::attempt()
dd([
'email' => $email,
'user_found' => $user !== null,
'password_match' => password_verify($password, $user['password'] ?? '')
]);Security Tip: Never reveal whether the email or password was wrong. Always say "Invalid credentials".