Curso de PHP

Sesiones y cookies en PHP: cómo recordar al usuario

Por Víctor Peña · Publicado el

Hola, ¿cómo están? Segundo tema complementario del curso de PHP: cómo hacer que tu aplicación recuerde quién es el usuario mientras navega.

¡Empecemos!

El problema: HTTP no recuerda nada

Cada petición al servidor es independiente de las anteriores. El navegador pide una página, PHP la genera, y todo lo que había en memoria se descarta.

Eso significa que si un usuario inicia sesión en una página, en la siguiente el servidor ya no sabe quién es. Se dice que HTTP es un protocolo sin estado.

Las sesiones y las cookies son las dos formas de resolverlo.

Cookies: datos en el navegador

Una cookie es un pequeño dato que el servidor le pide al navegador que guarde, y que el navegador reenvía en cada petición siguiente.

<?php
// Guardar
setcookie('idioma', 'es', [
    'expires' => time() + 86400 * 30,   // 30 días
    'path' => '/',
    'secure' => true,      // solo por HTTPS
    'httponly' => true,    // JavaScript no puede leerla
    'samesite' => 'Lax',   // protege contra CSRF
]);

// Leer
$idioma = $_COOKIE['idioma'] ?? 'es';

// Borrar
setcookie('idioma', '', ['expires' => time() - 3600, 'path' => '/']);

Dos detalles importantes:

setcookie() debe llamarse antes de cualquier salida. Si ya imprimiste HTML, obtendrás el error «headers already sent». Es el mismo motivo por el que recomendamos no cerrar con ?> los archivos que solo contienen PHP.

La cookie no está disponible en la misma petición. La guardas ahora y la lees a partir de la siguiente carga.

Las opciones de seguridad no son opcionales

  • httponly impide que JavaScript la lea. Es lo que limita el daño de un ataque XSS.
  • secure hace que solo viaje por HTTPS.
  • samesite evita que se envíe desde otros sitios, protegiendo contra CSRF.

Y la regla de fondo: nunca guardes información sensible en una cookie. El usuario puede verla y modificarla. Nada de precios, permisos ni identificadores de usuario sin firmar.

Sesiones: datos en el servidor

Una sesión guarda los datos en el servidor y solo envía al navegador un identificador. Es lo que quieres para todo lo importante.

<?php
session_start();   // siempre antes de cualquier salida

$_SESSION['usuario_id'] = 42;
$_SESSION['nombre'] = 'Víctor';

// En cualquier otra página:
session_start();
echo $_SESSION['nombre'] ?? 'Invitado';

session_start() hace dos cosas: crea la sesión si no existe, o recupera la existente a partir de la cookie de identificación.

Tiene que ir en la primera línea, antes de imprimir nada.

Comprobar y eliminar datos

<?php
session_start();

if (isset($_SESSION['usuario_id'])) {
    echo "Sesión activa";
}

unset($_SESSION['carrito']);   // borra un dato
$_SESSION = [];                // vacía todos

Cerrar sesión correctamente

Esto se hace mal muy a menudo. No basta con vaciar el array:

<?php
session_start();

// 1. Vaciar los datos
$_SESSION = [];

// 2. Borrar la cookie de sesión
if (ini_get('session.use_cookies')) {
    $params = session_get_cookie_params();
    setcookie(
        session_name(),
        '',
        time() - 42000,
        $params['path'],
        $params['domain'],
        $params['secure'],
        $params['httponly']
    );
}

// 3. Destruir la sesión en el servidor
session_destroy();

header('Location: login.php');
exit;

Los tres pasos son necesarios. Si solo vacías $_SESSION, el identificador sigue vivo y podría reutilizarse.

Regenerar el identificador al iniciar sesión

Esta línea evita un ataque real llamado fijación de sesión, y casi nadie la pone:

<?php
session_start();

// Tras verificar usuario y contraseña:
session_regenerate_id(true);

$_SESSION['usuario_id'] = $usuario['id'];

El ataque consiste en que alguien fuerce a la víctima a usar un identificador de sesión conocido; cuando la víctima inicia sesión, el atacante ya tiene una sesión válida. Regenerar el identificador en ese momento lo anula.

Hazlo siempre después de un login exitoso.

Configuración segura de sesiones

Al inicio de tu aplicación, antes del session_start():

<?php
ini_set('session.cookie_httponly', '1');
ini_set('session.cookie_secure', '1');      // requiere HTTPS
ini_set('session.use_strict_mode', '1');
ini_set('session.cookie_samesite', 'Lax');

session_start();

use_strict_mode hace que PHP rechace identificadores de sesión que él no generó, que es otra protección contra la fijación.

Un login básico

Juntemos esto con lo que vimos de bases de datos y formularios:

<?php
declare(strict_types=1);

session_start();

$error = null;

if ($_SERVER['REQUEST_METHOD'] === 'POST') {
    $correo = trim($_POST['correo'] ?? '');
    $clave = $_POST['clave'] ?? '';

    $sentencia = $pdo->prepare("SELECT id, nombre, clave_hash FROM usuarios WHERE correo = :correo");
    $sentencia->execute(['correo' => $correo]);
    $usuario = $sentencia->fetch();

    if ($usuario && password_verify($clave, $usuario['clave_hash'])) {
        session_regenerate_id(true);

        $_SESSION['usuario_id'] = $usuario['id'];
        $_SESSION['nombre'] = $usuario['nombre'];

        header('Location: panel.php');
        exit;
    }

    $error = 'Correo o contraseña incorrectos';
}

Dos cosas críticas aquí:

Las contraseñas nunca se guardan en texto plano. Se guarda un hash generado con password_hash():

<?php
$hash = password_hash($clave, PASSWORD_DEFAULT);

Y se comprueba con password_verify(). Nunca compares contraseñas con === ni uses md5() o sha1(), que están rotos para este uso.

El mensaje de error es genérico. Si dijeras «el correo no existe», estarías confirmando qué correos están registrados en tu sistema.

Proteger páginas privadas

<?php
// auth.php
session_start();

if (!isset($_SESSION['usuario_id'])) {
    header('Location: login.php');
    exit;
}

Y en cada página protegida:

<?php
require 'auth.php';
Sesión Cookie
Dónde se guarda Servidor Navegador
Puede modificarla el usuario No
Sobrevive al cerrar el navegador No, por defecto
Para datos sensibles Nunca

La regla: sesión para todo lo que importe —quién es el usuario, sus permisos, el carrito—, cookie solo para preferencias sin valor, como el idioma o el modo oscuro.

Errores comunes

  • Llamar a session_start() después de imprimir algo.
  • Guardar datos sensibles en cookies.
  • Cerrar sesión solo vaciando $_SESSION.
  • No regenerar el identificador tras el login.
  • Guardar contraseñas en texto plano o con md5().
  • Confiar en una cookie para decidir permisos.

Para cerrar

Las sesiones son la forma de que tu aplicación recuerde al usuario de forma segura; las cookies, para preferencias sin importancia. Y dos líneas que no puedes olvidar: session_regenerate_id(true) tras el login, y password_hash() para las contraseñas.

En el siguiente artículo reunimos todas las prácticas de seguridad del curso.

Saludos y éxitos.

Norvic Software

Desarrollamos el software que tu empresa necesita

Somos una fábrica de software en Bolivia. Construimos sistemas a medida y aplicaciones móviles, y llevamos Inteligencia Artificial a las empresas que ya tienen un sistema funcionando.

Solicitar cotizaciónVer todos los servicios

Cotización sin costo · Respuesta directa por WhatsApp