Seguridad en PHP: Sesiones, Contraseñas y CSRF

Tres piezas imprescindibles antes de poner un login en producción

🗂️ 1. Sesiones con $_SESSION

Estado actual de la sesión:

session_id()
fcf966fbfa5a…
$_SESSION['demo_usuario']
(vacío)

Guarda algo en tu sesión

💡 ¿Por qué funciona esto entre recargas?

PHP guarda tus datos de sesión en el servidor y solo envía al navegador una cookie con un ID ( PHPSESSID). Por eso session_start() debe llamarse antes de imprimir cualquier HTML: necesita poder enviar esa cookie en las cabeceras.

🔒 2. Hash de contraseñas: password_hash() / password_verify()

Hash "guardado" en el servidor (contraseña real: password123):

password_hash('password123', PASSWORD_BCRYPT)
$2y$12$mmgD0sMLY7RXOzw.vqQdDuM02l8t6814qWsU1WrqU3IunhmIqJSDS

Prueba a "iniciar sesión"

💡 Nunca guardes contraseñas en texto plano

password_hash() genera un hash distinto cada vez (incluye una "sal" aleatoria), aunque la contraseña sea la misma. Por eso nunca comparas contraseñas con ==: usas password_verify(), que sabe extraer esa sal del propio hash.

🛡️ 3. Protección CSRF con tokens

Token válido actual en $_SESSION:

$_SESSION['csrf_token']
b4cbe12c407a926860085843c4338267

Envía el formulario protegido

Este formulario incluye el token correcto oculto.

💡 ¿Qué evita un token CSRF?

Que otra web engañe al navegador del usuario para enviar un formulario a la tuya sin que él lo sepa. Como ese token secreto vive en la sesión del servidor y en un campo oculto del formulario legítimo, un atacante externo no puede adivinarlo.

🎯 Conceptos Clave

Sesiones

  • session_start() antes de cualquier salida
  • Datos guardados en el servidor, no en el navegador

Hashing

  • password_hash() para guardar
  • password_verify() para comprobar

CSRF

  • Token secreto por sesión
  • Comparar con hash_equals(), nunca con ==

💪 Ejercicio Práctico

Refuerza el sistema

  1. Añade un contador de intentos fallidos de contraseña en $_SESSION y bloquea tras 3 intentos.
  2. Regenera el session_id() con session_regenerate_id(true) justo después de un login correcto.
  3. Añade una cookie de sesión con httponly y samesite=Strict en session_set_cookie_params().

Solución 1

$_SESSION['intentos'] = ($_SESSION['intentos'] ?? 0) + 1;
if ($_SESSION['intentos'] >= 3) {
    die(t('Demasiados intentos, inténtalo más tarde.'));
}

Solución 2

if (password_verify($password, $hashGuardado)) {
    session_regenerate_id(true);
    $_SESSION['usuario_id'] = $usuario['id'];
}

Solución 3

session_set_cookie_params([
    'httponly' => true,
    'samesite' => 'Strict',
    'secure'   => true
]);
session_start();