Broken Authentication
Fix password verification vulnerability
Challenge: Broken Authentication
Difficulty: Easy
Bugs: 1
Time: 45 minutes
The Problem
The authentication system has a critical bug that prevents users from logging in.
Symptom: Login always fails with "Invalid credentials" even when using the correct password.
Setup
Backup Current Files
cp framework/Core/Authenticator.php framework/Core/Authenticator.php.backupCopy Broken Files
cp challenges/broken-auth/framework/Core/Authenticator.php framework/Core/
cp -r challenges/broken-auth/Http/controllers/auth Http/controllers/Add Routes
cat challenges/broken-auth/routes/routes.php >> routes/routes.phpEnsure Database Exists
php artisan migrateTest the Bug
- Visit
http://localhost:8000/auth/register - Register a new user (this works!)
- Try to login with the same credentials (this fails!)
The Bug: Plain Text Password Comparison
What's Happening
// BROKEN - Plain text comparison
public function attempt($email, $password) {
$user = $this->findUser($email);
if ($user && $password == $user['password']) {
// This never matches!
$this->login($user);
return true;
}
return false;
}Why It's Broken
During registration, passwords are hashed:
$hash = password_hash('password123', PASSWORD_BCRYPT);
// Result: $2y$10$abcdefghijklmnopqrstuvwxyz...During login, the code compares plain text against the hash:
'password123' == '$2y$10$abcdefghijklmnopqrstuvwxyz...'
// Always false!The plain text password will never equal the hashed password.
Security Issue: Even if this worked, using == for password comparison is insecure. Always use password_verify().
Understanding Password Hashing
Why Hash Passwords?
Never store plain text passwords! If your database is compromised, attackers get all passwords.
How password_hash() Works
$password = 'password123';
$hash = password_hash($password, PASSWORD_BCRYPT);
// Result: $2y$10$abcdefghijklmnopqrstuvwxyz123456789...
// Same password, different hash each time (due to random salt)
$hash2 = password_hash($password, PASSWORD_BCRYPT);
// Result: $2y$10$zyxwvutsrqponmlkjihgfedcba987654321...How password_verify() Works
$password = 'password123';
$hash = '$2y$10$abcdefghijklmnopqrstuvwxyz123456789...';
password_verify($password, $hash); // true
password_verify('wrong', $hash); // falsepassword_verify() extracts the salt from the hash, recomputes, and compares securely.
The Fix
Replace plain text comparison with password_verify():
// ✅ CORRECT
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([
'email' => $email,
]);
return true;
}
return false;
}Why This Works: password_verify() correctly compares the plain text password against the hashed password.
Verification
After fixing the bug, run verification:
php artisan verify broken-authExpected output:
╔══════════════════════════════════════════════════════════════╗
║ DALT Challenge Verification System ║
╚══════════════════════════════════════════════════════════════╝
Verifying: broken-auth
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✓ File contains password_verify function call
✓ File does not contain plain text comparison
✓ Authentication logic is correct
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
Results: 3/3 tests passed
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
✅ All tests passed! Challenge complete!Testing Your Fix
1. Register a New User
Visit: http://localhost:8000/auth/register
Email: test@example.com
Password: password1232. Verify Password is Hashed
sqlite3 database/app.sqlite
SELECT email, password FROM users;
# Should see hashed password like: $2y$10$...3. Login with Same Credentials
Visit: http://localhost:8000/auth/login
Email: test@example.com
Password: password123
# Should redirect to dashboard4. Verify Session
// Add to any controller
dd($_SESSION);
// Should see: ['user' => ['email' => 'test@example.com']]Success Criteria
When fixed correctly:
- ✅ Users can register with hashed passwords
- ✅ Users can login with correct credentials
- ✅ Login fails with incorrect credentials
- ✅ Session persists after login
- ✅ Protected routes are accessible after login
Learning Objectives
After completing this challenge, you understand:
- ✅ Why passwords must be hashed
- ✅ How
password_hash()andpassword_verify()work - ✅ Why plain text comparison fails with hashed passwords
- ✅ How authentication flow works from registration to login
- ✅ Why timing-safe comparison matters
Debugging Tips
Check What's Stored
// In Authenticator::attempt()
dd($user);
// See the hashed passwordCompare Values
dd([
'plain' => $password,
'hash' => $user['password'],
'match' => $password == $user['password'] // Always false!
]);Test password_verify
dd(password_verify($password, $user['password']));
// Should be true for correct passwordFiles to Investigate
framework/Core/Authenticator.php- Login logic (bug is here!)Http/controllers/auth/register-post.php- See how passwords are hashedHttp/controllers/auth/login-post.php- See how login is attemptedframework/Core/Middleware/Auth.php- See how authentication is checked
Cleanup
After completing the challenge:
# Restore original Authenticator
cp framework/Core/Authenticator.php.backup framework/Core/Authenticator.php
# Remove challenge controllers (optional)
rm -rf Http/controllers/authNext Challenge
Continue to Lesson 5: Database or try Challenge: Broken Database.