Llama a session_start() al principio de una página y luego lee y escribe $_SESSION['key'] como cualquier array: PHP lo guarda en el servidor y lo vuelve a encontrar en la siguiente petición del visitante. Para un valor pequeño que deba guardar el navegador, llama a setcookie('name', 'value', time() + 86400) y léelo en las peticiones posteriores desde $_COOKIE['name'].
<?php
session_start(); // before any output
setcookie('theme', 'dark', time() + 60 * 60 * 24 * 30); // 30 days, also before output
$_SESSION['visits'] = ($_SESSION['visits'] ?? 0) + 1;
$theme = $_COOKIE['theme'] ?? 'light'; // set on an earlier visit
echo "You have opened this page {$_SESSION['visits']} times.";
Las sesiones y las cookies solo tienen sentido a lo largo de varias peticiones HTTP de un navegador, así que los ejemplos que las necesitan son código simple. La lógica que las rodea es PHP normal sobre arrays, y esa parte sí se ejecuta aquí: en cada bloque ejecutable, $session hace el papel de $_SESSION, y cada llamada es una petición.
Cómo funciona una sesión de PHP
- El primer
session_start()crea un id aleatorio, lo envía al navegador en una cookie llamadaPHPSESSIDy te da un$_SESSIONvacío. - Al final de la petición, PHP guarda
$_SESSIONen el servidor (en archivos por defecto, o en Redis, o en una base de datos). - En la siguiente petición el navegador devuelve la cookie,
session_start()lee el id y carga el array guardado.
El navegador nunca ve los datos, solo el id. Por eso el id del usuario que ha iniciado sesión va en $_SESSION y nunca en una cookie propia: un visitante puede cambiar sus cookies a cualquier valor, pero no puede editar $_SESSION.
Iniciar y cerrar la sesión de un usuario
Iniciar sesión es: comprobar la contraseña, regenerar el id de sesión y guardar el id del usuario. Cerrar sesión es lo contrario, y tiene tres pasos.
<?php
// login.php
session_start();
$user = find_user_by_email($_POST['email'] ?? '');
if ($user && password_verify($_POST['password'] ?? '', $user['password_hash'])) {
session_regenerate_id(true); // new id after login: stops session fixation
$_SESSION['user_id'] = $user['id'];
header('Location: /dashboard.php');
exit;
}
$error = 'Wrong email or password.';
<?php
// any protected page
session_start();
if (!isset($_SESSION['user_id'])) {
header('Location: /login.php');
exit;
}
<?php
// logout.php
session_start();
$_SESSION = []; // 1. forget the data
$p = session_get_cookie_params(); // 2. delete the cookie
setcookie(session_name(), '', time() - 3600, $p['path'], $p['domain'], $p['secure'], $p['httponly']);
session_destroy(); // 3. delete the stored session
header('Location: /');
exit;
session_regenerate_id(true) importa más de lo que parece. Si un atacante consigue que una víctima use un id de sesión que el atacante conoce (por ejemplo, mediante un enlace), y el id sigue siendo el mismo después de iniciar sesión, el atacante también queda dentro. Un id nuevo al iniciar sesión cierra ese agujero. La comprobación de la contraseña en sí se explica en la página de password_hash.
Expirar una sesión tras un periodo de inactividad
La limpieza propia de PHP (session.gc_maxlifetime, 1440 segundos por defecto) se ejecuta al azar y no es una expiración fiable. Guarda la hora de la última petición y compruébala tú. El bloque usa timestamps fijos para que veas cada caso; cambia $timeout o las horas.
El intervalo de 09:05 a 09:16:40 es de 700 segundos, por debajo del límite de 15 minutos, así que el usuario sigue dentro. El siguiente intervalo es de 1000 segundos, así que la sesión se vacía.
Mensajes flash
Un mensaje flash se fija en una petición y se muestra una sola vez en la siguiente, normalmente "Guardado." después de una redirección. Guárdalo en la sesión, y luego léelo y elimínalo en un solo paso:
Fijar una cookie con setcookie
setcookie() recibe el nombre, el valor y un array de opciones (PHP 7.3 y posteriores). Fija estas cuatro opciones en todas las cookies salvo que tengas un motivo para no hacerlo:
<?php
setcookie('theme', 'dark', [
'expires' => time() + 60 * 60 * 24 * 30, // 30 days; 0 means "until the browser closes"
'path' => '/', // send it on every page, not just this folder
'secure' => true, // only over HTTPS
'httponly' => true, // JavaScript cannot read it
'samesite' => 'Lax', // not sent on most cross-site requests
]);
$theme = $_COOKIE['theme'] ?? 'light';
Dos cosas confunden a la gente:
$_COOKIEno cambia en la misma petición. Contiene lo que envió el navegador con esta petición. Una cookie que fijas ahora aparece en$_COOKIEen la siguiente.- Las cookies son cabeceras, así que
setcookie()debe ejecutarse antes de cualquier salida, igual quesession_start()yheader().
Para borrar una cookie, vuelve a fijarla con el mismo nombre y path y una fecha de caducidad en el pasado: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']).
Guardar un array en una cookie
El valor de una cookie es una cadena de unos 4 KB como mucho, así que guarda los datos estructurados como JSON, y trata lo que vuelve como algo no fiable: el visitante puede cambiarlo. Nunca hagas unserialize() de una cookie: puede crear objetos de tus propias clases y ejecutar sus métodos mágicos, mientras que json_decode() solo devuelve arrays, cadenas, números, booleanos, null o stdClass.
La página de json_encode explica las opciones. Para cualquier cosa más grande que unos pocos valores cortos, guarda los datos en la sesión (o en una base de datos) y guarda en la cookie solo una clave.
Tokens de "recordarme"
Una cookie de "recordarme" no debe contener el id del usuario ni el hash de la contraseña. Guarda en la cookie un token aleatorio largo y en la base de datos solo su hash, y compáralos con hash_equals():
Los valores cambian en cada ejecución, porque random_bytes() es aleatorio. Si se filtra la base de datos, los hashes no sirven de nada sin las cookies, y hash_equals() compara en tiempo constante para que la comprobación no filtre información a través del tiempo que tarda.
Sesión vs cookie: cuál usar
Sesión ($_SESSION) | Cookie ($_COOKIE) | |
|---|---|---|
| Se guarda | En el servidor | En el navegador |
| El visitante puede leerla o editarla | No (solo el id) | Sí |
| Tamaño | Limitado por tu almacenamiento | Unos 4 KB por cookie |
| Dura | Hasta que se cierra el navegador o expira la sesión | Hasta su fecha expires |
| Úsala para | Usuario que ha iniciado sesión, carrito, token CSRF, mensajes flash | Tema, idioma, elección de consentimiento, token de "recordarme" |
Error frecuente: headers already sent
session_start(), setcookie() y header() envían cabeceras HTTP, y las cabeceras solo se pueden enviar antes del cuerpo. Cualquier salida previa, incluso una línea en blanco antes de <?php en un archivo incluido, produce:
Warning: session_start(): Session cannot be started after headers have already been sent in /var/www/page.php on line 3
Warning: Cannot modify header information - headers already sent by (output started at /var/www/page.php:2) in /var/www/page.php on line 4
El segundo mensaje te dice dónde empezó la salida (page.php:2): ve a esa línea y quita la salida, o mueve session_start() por encima de ella. En los archivos que solo contienen PHP, omitir el ?> de cierre evita que los espacios sueltos que haya detrás se conviertan en salida.
Preguntas frecuentes
¿Cuál es la diferencia entre una sesión y una cookie en PHP?
Una cookie se guarda en el navegador del visitante y se envía con cada petición, así que el visitante puede leerla y cambiarla. Una sesión guarda sus datos en el servidor; el navegador solo tiene un id de sesión aleatorio en una cookie (PHPSESSID). Pon en $_SESSION todo aquello que deba ser de confianza, como el id del usuario que ha iniciado sesión.
¿Cómo destruyo una sesión al cerrar sesión en PHP?
Vacía los datos, borra la cookie de sesión y destruye la sesión guardada: $_SESSION = [];, luego setcookie(session_name(), '', time() - 3600, '/') y luego session_destroy();. Redirige después para que la siguiente página empiece de cero.
¿Cómo borro una cookie en PHP?
Vuelve a fijarla con el mismo nombre, ruta y dominio y una fecha de caducidad en el pasado: setcookie('theme', '', ['expires' => time() - 3600, 'path' => '/']);. Haz también unset($_COOKIE['theme']) si el resto del script actual la lee, porque $_COOKIE solo cambia en la siguiente petición.
¿Cuánto dura una sesión de PHP?
Por defecto, la cookie de sesión dura hasta que se cierra el navegador (session.cookie_lifetime = 0) y el servidor puede borrar los datos de sesión tras 1440 segundos (24 minutos) sin actividad (session.gc_maxlifetime). Para un tiempo de expiración fijo, guarda en la sesión la hora de la última petición y compruébala tú.
¿Por qué session_start() dice headers already sent?
El id de sesión viaja en una cabecera de cookie, y las cabeceras deben enviarse antes de cualquier salida. Un echo, HTML o incluso una línea en blanco o un BOM antes de <?php envían primero la salida. Llama a session_start() al principio del script, antes de imprimir nada.