PHP Security: Sessions, Passwords and CSRF
Three essential pieces before putting a login into production
🗂️ 1. Sessions with $_SESSION
Current session status:
Save something in your session
💡 Why does this work across page reloads?
PHP stores your session data on the server and only sends the browser a cookie with an ID ( PHPSESSID). That's why session_start() must be called before printing any HTML: it needs to be able to send that cookie in the headers.
🔒 2. Password hashing: password_hash() / password_verify()
Hash "saved" on the server (actual password: password123):
Try to "log in"
💡 Never store passwords in plain text
password_hash() generates a different hash each time (it includes a random "salt" ), even if the password is the same. That's why you never compare passwords with ==: you use password_verify(), which knows how to extract that salt from the hash itself.
🛡️ 3. Protection CSRF with tokens
Current valid token in $_SESSION:
Send the protected form
💡 What does a CSRF token prevent?
That another website tricks the user's browser into submitting a form to yours without them knowing. Since that secret token lives in the server session and in a hidden field of the legitimate form, an outside attacker can't guess it.
🎯 Key Concepts
Sessions
session_start()before any output- Data stored on the server, not in the browser
Hashing
password_hash()to storepassword_verify()to check
CSRF
- Secret token per session
- Compare with
hash_equals(), never with==
💪 Practical Exercise
Strengthen the system
- Add a counter for failed password attempts in
$_SESSIONand block after 3 attempts. - Regenerate the
session_id()withsession_regenerate_id(true)right after a successful login. - Add a session cookie with
httponlyandsamesite=Strictinsession_set_cookie_params().
Solution 1
$_SESSION['intentos'] = ($_SESSION['intentos'] ?? 0) + 1;
if ($_SESSION['intentos'] >= 3) {
die(t('Demasiados intentos, inténtalo más tarde.'));
}
Solution 2
if (password_verify($password, $hashGuardado)) {
session_regenerate_id(true);
$_SESSION['usuario_id'] = $usuario['id'];
}
Solution 3
session_set_cookie_params([
'httponly' => true,
'samesite' => 'Strict',
'secure' => true
]);
session_start();