Cookies seguras: getCookie, setCookie y deleteCookie
Debajo de las sesiones selladas viven las cookies crudas, y saber gobernarlas es saber dónde vive una credencial. SolidStart expone los helpers de vinxi —`getCookie`, `setCookie` y `deleteCookie`— que operan sobre el evento de la petición actual. Esta lección enseña a leerlas y escribirlas tanto desde funciones de servidor (con `getRequestEvent().nativeEvent`) como desde middleware (con `event.nativeEvent`), y disecciona los atributos que separan una cookie segura de una vulnerable: `httpOnly` contra el robo por XSS, `secure` para exigir HTTPS, `sameSite` contra el CSRF, y el prefijo `__Host-` como candado final.
Una sesión sellada es cómoda porque esconde la cookie, pero por debajo siempre hay una cookie cruda, y hay estado —una preferencia de tema, un token de un tercero, un identificador de dispositivo— que quieres manejar sin todo el aparato de sellado. Para eso SolidStart reexpone los helpers de cookies de vinxi: getCookie para leer, setCookie para escribir con sus atributos, deleteCookie para caducar. No son azúcar: cada opción que les pasas —httpOnly, secure, sameSite, path, maxAge— decide cuándo y a quién entregará el navegador esa credencial. Dominar esos atributos es dominar la superficie de ataque de tu autenticación, porque una cookie mal configurada es una puerta que creías cerrada.
- Leer, escribir y borrar cookies con
getCookie,setCookieydeleteCookiedevinxi/http. - Operar sobre el evento correcto:
event.nativeEventen middleware,getRequestEvent().nativeEventen funciones de servidor. - Elegir los atributos de seguridad:
httpOnlycontra XSS,securepara HTTPS,sameSitecontra CSRF. - Endurecer una cookie de sesión con
maxAge,pathy el prefijo__Host-.
Los tres helpers de vinxi/http
Los tres helpers reciben como primer argumento el evento nativo de h3 —el que representa la petición viva— y actúan sobre sus cabeceras Cookie y Set-Cookie. getCookie devuelve el valor o undefined; setCookie programa una cookie con sus opciones; deleteCookie emite una cookie ya caducada para que el navegador la olvide.
import { getCookie, setCookie, deleteCookie } from "vinxi/http";
// getCookie(evento, nombre) -> string | undefined
// setCookie(evento, nombre, valor, opciones)
// deleteCookie(evento, nombre, opciones)
La firma pide el evento porque una cookie no existe en el vacío: pertenece a una petición y a una respuesta concretas. De ahí que estos helpers, como las sesiones, solo tengan sentido en el servidor, donde esas cabeceras existen.
Los atributos que hacen segura una cookie
Escribir una cookie sin atributos es dejar una credencial a la intemperie. Cada opción cierra un vector de ataque distinto, y una cookie de autenticación seria los combina todos.
setCookie(evento, "token", valorFirmado, {
httpOnly: true, // invisible a document.cookie: el XSS no puede robarla
secure: true, // el navegador solo la envia por HTTPS
sameSite: "lax", // no viaja en peticiones cross-site peligrosas: frena CSRF
path: "/", // alcance: toda la app
maxAge: 60 * 60 * 24 * 7, // vida en segundos; sin esto seria de sesion
});
httpOnlyla oculta del JavaScript de la página: aunque un atacante inyecte un script (XSS), no podrá leerdocument.cookiepara exfiltrar el token. Es la defensa número uno de cualquier credencial.secureprohíbe que la cookie viaje por HTTP en claro; sin ella, una red hostil la vería.sameSitecontrola si la cookie acompaña a peticiones iniciadas desde otro sitio.lax—valor por defecto sensato— la envía en navegaciones de nivel superior pero no en peticiones incrustadas;strictla retiene incluso al llegar desde un enlace externo;nonela manda siempre, pero obliga asecurey solo se usa en flujos cross-site legítimos.
Cookies desde funciones de servidor y desde middleware
El único matiz práctico es de dónde sacas el evento. En middleware ya lo tienes: event.nativeEvent. En una función de servidor lo recuperas con getRequestEvent de solid-js/web y accedes a su .nativeEvent.
// Desde una funcion de servidor
import { getRequestEvent } from "solid-js/web";
import { getCookie, setCookie } from "vinxi/http";
export async function fijarTema(tema: "claro" | "oscuro") {
"use server";
const event = getRequestEvent()!;
setCookie(event.nativeEvent, "tema", tema, {
secure: true,
sameSite: "lax",
maxAge: 60 * 60 * 24 * 365, // un ano
path: "/",
// sin httpOnly: aqui si queremos que el cliente lea el tema
});
}
export async function leerTema() {
"use server";
const event = getRequestEvent()!;
return getCookie(event.nativeEvent, "tema") ?? "claro";
}
// Desde middleware
import { createMiddleware } from "@solidjs/start/middleware";
import { getCookie } from "vinxi/http";
export default createMiddleware({
onRequest: (event) => {
const tema = getCookie(event.nativeEvent, "tema"); // ya tienes el evento
event.locals.tema = tema ?? "claro";
},
});
Observa el contraste deliberado: la cookie de tema no lleva httpOnly, porque queremos que el cliente la lea para pintar sin parpadeo; la cookie de sesión de la lección anterior sí lo lleva, porque su valor es una credencial que el cliente nunca debe tocar. El atributo no es un adorno: codifica quién tiene derecho a leer.
flowchart TD
A[setCookie con atributos] --> B{httpOnly}
B -->|si| C[oculta a document.cookie: frena XSS]
A --> D{secure}
D -->|si| E[solo por HTTPS]
A --> F{sameSite}
F -->|lax o strict| G[no viaja cross-site: frena CSRF]
style C fill:#a6e3a1,color:#11111b
style E fill:#a6e3a1,color:#11111b
style G fill:#a6e3a1,color:#11111bEl prefijo __Host- y el candado completo
Para una cookie de sesión, el endurecimiento máximo es nombrarla con el prefijo __Host-. El navegador solo acepta una cookie así si viene con secure, con path a la raíz y sin atributo domain, lo que la ata a un único host y la blinda contra ataques donde un subdominio comprometido fija cookies para el dominio padre. Es una garantía que el propio navegador hace cumplir.
setCookie(evento, "__Host-sesion", valorSellado, {
httpOnly: true,
secure: true,
sameSite: "lax",
path: "/", // obligatorio para __Host-
// sin domain: obligatorio para __Host-
});
Para borrar una cookie, deleteCookie debe recibir los mismos path y domain con que se creó; si no coinciden, el navegador la trata como otra cookie distinta y la original sobrevive. Y distingue la cookie persistente —con maxAge o expires, que aguanta cierres del navegador, la que quieres para un «recuérdame»— de la cookie de sesión —sin ninguno de los dos, que muere al cerrar la pestaña, la adecuada para un acceso efímero—.
Un fallo silencioso al cerrar sesión es llamar a deleteCookie con un path distinto del que usó setCookie. El navegador identifica una cookie por la terna de nombre, dominio y ruta, así que borrarla exige reproducir esa terna exacta. Si la sesión se fijó con path a la raíz, bórrala con path a la raíz; de lo contrario dejarás viva una credencial que creías revocada, y ese es justo el tipo de descuido que convierte un logout en un falso logout.
httpOnly
Saca la cookie del alcance de document.cookie. Un XSS ya no puede leer el token. Imprescindible en credenciales.
secure + sameSite
Secure la ata a HTTPS; sameSite decide si acompana peticiones de otros sitios. Juntas cierran red y CSRF.
__Host-
Un prefijo que el navegador vigila: exige secure, path raiz y sin domain. Blinda contra subdominios hostiles.
Dos errores frecuentes hunden la seguridad de una cookie. El primero: poner sameSite: "none" sin secure —el navegador la rechazará, y si funcionara sería un coladero de CSRF—; reserva none para flujos cross-site reales y siempre con secure. El segundo, más grave: guardar un token de autenticación en una cookie sin httpOnly, o peor, en localStorage. Cualquier script inyectado en tu página lo leería y lo robaría. La regla es simple y no admite excepciones: las credenciales viven en cookies httpOnly secure; lo que el cliente necesita leer —un tema, un idioma— vive en cookies aparte, sin secretos dentro.
La idea que convierte el manejo de cookies de receta en criterio es entender que una cookie es autoridad ambiental: el navegador la adjunta a las peticiones de forma automática, sin que tu código lo pida, cada vez que el destino coincide con su alcance. Esa automaticidad es a la vez su virtud y su veneno. Es la virtud porque hace que las sesiones «simplemente funcionen»: el usuario inicia sesión una vez y, a partir de ahí, cada petición lleva la credencial sola, sin que tú la reenvíes a mano. Y es el veneno porque el navegador no distingue tus intenciones de las de un atacante: si un sitio malicioso provoca una petición a tu dominio, el navegador adjuntará igualmente la cookie, y esa es la esencia del CSRF —autoridad ejercida sin consentimiento del usuario—. Todos los atributos de una cookie son, leídos con esta lente, la misma cosa: instrumentos para estrechar cuándo y a quién el navegador rinde la credencial. secure dice «solo si el canal está cifrado». sameSite dice «solo si la petición nace de mi propio sitio», y con ello desactiva de raíz la petición cross-site que el CSRF necesita. httpOnly cambia el eje y dice «y que ni siquiera mi propio JavaScript pueda tocarla», cerrando la vía por la que un XSS robaría el valor. __Host- sube la apuesta pidiéndole al navegador que rechace configuraciones laxas. Cada atributo recorta una dimensión del «cuándo se entrega»: el canal, el origen, el lenguaje que la lee, el host que la fija. Cuando piensas en cookies así —no como un almacén de pares clave-valor sino como una credencial ambiental cuya rendición gobiernas atributo a atributo— dejas de copiar opciones de un tutorial y empiezas a razonar tu superficie de ataque. Y descubres que la sesión sellada de la lección anterior no era magia, sino exactamente esta cookie endurecida con un contenido que además va cifrado.
- Escribe
fijarTemaconsetCookiesinhttpOnlyy confirma en la consola del navegador quedocument.cookiela muestra; es correcto, porque el tema no es un secreto. - Escribe una cookie de token con
httpOnly,secureysameSite: "lax", y verifica quedocument.cookieya no la ve: acabas de sacarla del alcance de cualquier XSS. - Lee esa cookie desde un middleware con
getCookie(event.nativeEvent, ...)y deja el resultado enevent.locals; comprueba que el valor llega intacto. - Renombra la cookie de sesión con el prefijo
__Host-y observa que el navegador la rechaza si le quitassecureo le añadesdomain: el propio navegador está haciendo cumplir tu política. - Borra la cookie con
deleteCookiey razona por qué, internamente, «borrar» es emitir unSet-Cookieya caducado en lugar de una operación en el servidor.