Cookies
event.cookies is the cookie jar of the request: what arrived and what to send back.
event.cookies is the cookie jar of the request: what arrived and what to send back.
| Method | Does |
|---|---|
get(name), getAll() |
read an incoming cookie, or one set earlier in this request |
set(name, value, options?) |
queue a Set-Cookie; the value is percent-encoded |
delete(name, options?) |
queue a removal (Max-Age=0); pass the path and domain the cookie was set with |
setSigned(name, value, options?), getSigned(name) |
HMAC-signed value; getSigned returns undefined when the signature does not verify |
serialized() |
the Set-Cookie values queued so far |
set writes Path=/; HttpOnly; Secure; SameSite=Lax unless an option says otherwise (domain, path, maxAge,
expires, httpOnly, secure, sameSite, partitioned). serializeCookie is the function behind it and
validates what it writes: a name that is not an RFC 6265 token, a Domain or Path that could carry another
attribute, a __Host- cookie that is not Secure, has a Domain or a Path other than /, a __Secure- cookie
that is not Secure, and SameSite=None without Secure all throw TypeError. One response sets at most
COOKIE_LIMITS.maxOutgoing (50) cookies, one cookie is at most 4096 bytes, and an incoming Cookie header is
ignored above 8192 bytes, read up to 50 cookies, with the first occurrence of a name winning. Secure is the default
everywhere except in development (dev: true) over http:// to localhost, 127.0.0.1, [::1] or *.localhost,
where browsers accept it anyway and the default is off.
import { COOKIE_LIMITS, serializeCookie } from "@shadow-js/dusk/server";
console.log(serializeCookie("theme", "dark", { maxAge: 31_536_000, httpOnly: false }));
console.log(serializeCookie("__Host-sid", "x"));
try {
serializeCookie("__Host-sid", "x", { domain: "example.com" });
} catch (error) {
console.log(`${error instanceof Error ? `${error.name}: ${error.message}` : error}`);
}
console.log(JSON.stringify(COOKIE_LIMITS));
theme=dark; Path=/; Max-Age=31536000; Secure; SameSite=Lax
__Host-sid=x; Path=/; HttpOnly; Secure; SameSite=Lax
TypeError: a __Host- cookie must be Secure, have Path=/ and no Domain.
{Signed cookies#
A signed cookie is value.signature, where the signature is the base64url HMAC-SHA-256 of name=value. It proves
the server wrote the value and that it was not moved to another cookie name; it does not hide it, so keep secrets
out of it (a session keeps the data on the server). Configure the secrets with
security.cookieSecrets: every secret needs at least MIN_SECRET_LENGTH (32) characters, the first one signs and
all of them verify. To rotate a secret, put the new one first and keep the old one until the cookies it signed have
expired. Calling setSigned without secrets throws COOKIE_SECRET_MISSING (a 500); a short secret throws
COOKIE_SECRET_WEAK when the handler is created. createCookieSigner is the same signer without a request.
import { createCookieSigner } from "@shadow-js/dusk/server";
const oldSecret = "old-secret-old-secret-old-secret-0000";
const newSecret = "new-secret-new-secret-new-secret-1111";
const before = createCookieSigner([oldSecret]);
const signed = await before.sign("sid", "user-1");
console.log((await before.unsign("sid", signed)) ?? "rejected");
console.log((await before.unsign("other", signed)) ?? "rejected");
console.log((await before.unsign("sid", signed.replace("user-1", "admin"))) ?? "rejected");
const rotated = createCookieSigner([newSecret, oldSecret]);
console.log((await rotated.unsign("sid", signed)) ?? "rejected");
const retired = createCookieSigner([newSecret]);
console.log((await retired.unsign("sid", signed)) ?? "rejected");
user-1
rejected
rejected
user-1
rejectedimport { json, type RequestEvent } from "@shadow-js/dusk/server";
export const GET = async (event: RequestEvent) => {
event.cookies.set("theme", "dark", { httpOnly: false, maxAge: 3600 });
await event.cookies.setSigned("plan", "pro", { sameSite: "strict" });
return json({ ok: true });
};
export const POST = async (event: RequestEvent) =>
json({
theme: event.cookies.get("theme") ?? null,
plan: (await event.cookies.getSigned("plan")) ?? null,
});