Las cookies son pequeños fragmentos de texto que el servidor envía al navegador y que este devuelve en cada petición posterior. En PHP se crean con setcookie(), se leen con $_COOKIE y se eliminan enviando una cookie con fecha de expiración en el pasado. Configurar correctamente sus atributos de seguridad es fundamental para proteger a los usuarios.
setcookie(): crear una cookie
setcookie() debe llamarse antes de cualquier salida HTML. Sus parámetros principales son el nombre, el valor, la expiración y la ruta. A partir de PHP 7.3 también acepta un array de opciones que incluye los atributos de seguridad modernos.
<?php
// Cookie básica que expira en 30 días
setcookie('idioma', 'es', time() + 30 * 24 * 3600, '/');
// Forma moderna con array de opciones (PHP 7.3+)
setcookie('preferencia', 'oscuro', [
'expires' => time() + 7 * 24 * 3600,
'path' => '/',
'domain' => 'programacion.net',
'secure' => true, // solo HTTPS
'httponly' => true, // no accesible desde JS
'samesite' => 'Lax', // protección CSRF básica
]);
?>
Leer cookies con $_COOKIE
La superglobal $_COOKIE contiene las cookies que el navegador ha enviado en la petición actual. Las cookies recién creadas con setcookie() no aparecen en $_COOKIE hasta la siguiente petición.
<?php
// Leer una cookie con valor por defecto
$idioma = $_COOKIE['idioma'] ?? 'es';
// Nunca usar el valor de una cookie directamente en HTML
$preferencia = htmlspecialchars($_COOKIE['preferencia'] ?? '', ENT_QUOTES, 'UTF-8');
echo "Tema seleccionado: $preferencia";
// Comprobar si existe una cookie concreta
if (isset($_COOKIE['recordar_usuario'])) {
$tokenRecordado = $_COOKIE['recordar_usuario'];
// Verificar el token en la BD antes de autenticar
}
?>
Cookies de sesión vs. cookies persistentes
Una cookie de sesión no tiene fecha de expiración y el navegador la elimina al cerrarse. Una cookie persistente tiene un expires en el futuro y sobrevive al cierre del navegador. Usar una u otra depende de si se quiere que los datos persistan entre sesiones.
<?php
// Cookie de sesión (sin expires o expires=0): desaparece al cerrar el navegador
setcookie('carrito_id', 'abc123', [
'path' => '/',
'httponly' => true,
'samesite' => 'Lax',
]);
// Cookie persistente: dura 90 días
setcookie('usuario_recordado', $token, [
'expires' => time() + 90 * 24 * 3600,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Strict',
]);
?>
Eliminar una cookie
Para borrar una cookie hay que enviar al navegador una nueva cookie con el mismo nombre, misma ruta y misma sesión, pero con una fecha de expiración en el pasado.
<?php
function eliminarCookie(string $nombre, string $ruta = '/'): void
{
setcookie($nombre, '', [
'expires' => time() - 3600, // en el pasado
'path' => $ruta,
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
unset($_COOKIE[$nombre]); // limpiar también en la petición actual
}
eliminarCookie('preferencia');
eliminarCookie('recordar_usuario');
?>
Firmar cookies con hash_hmac()
Para detectar si el usuario ha manipulado el valor de una cookie, se puede firmarla con hash_hmac() usando una clave secreta. Al leerla, se verifica la firma antes de confiar en el valor.
<?php
const COOKIE_SECRET = 'clave_super_secreta_de_al_menos_32_caracteres';
function crearCookieFirmada(string $nombre, string $valor, int $expira): void
{
$firma = hash_hmac('sha256', $valor, COOKIE_SECRET);
$valorFirmado = base64_encode($valor . '|' . $firma);
setcookie($nombre, $valorFirmado, [
'expires' => $expira,
'path' => '/',
'secure' => true,
'httponly' => true,
'samesite' => 'Lax',
]);
}
function leerCookieFirmada(string $nombre): ?string
{
if (!isset($_COOKIE[$nombre])) {
return null;
}
$decodificado = base64_decode($_COOKIE[$nombre]);
$partes = explode('|', $decodificado, 2);
if (count($partes) !== 2) {
return null;
}
[$valor, $firmaRecibida] = $partes;
$firmaEsperada = hash_hmac('sha256', $valor, COOKIE_SECRET);
// Comparación segura frente a timing attacks
if (!hash_equals($firmaEsperada, $firmaRecibida)) {
return null; // cookie manipulada
}
return $valor;
}
// Crear cookie firmada que dura 7 días
crearCookieFirmada('nivel', 'premium', time() + 7 * 24 * 3600);
// Leer y verificar
$nivel = leerCookieFirmada('nivel'); // 'premium' o null si hay manipulación
?>
La documentación oficial de setcookie() detalla todos los parámetros disponibles, las diferencias entre navegadores para el atributo SameSite y las consideraciones sobre codificación del valor de la cookie.
