Search docs

Jump between documentation pages.

Browse docs

Cookie helpers

Think of it like… a tamper-evident envelope service. Every department that mails something out uses the same envelopes with the same seals, so nobody invents their own flimsy version — and the mail room refuses to accept two identical envelopes claiming to be the same letter.

DaloyJS exposes the same low-level cookie helpers that session() and csrf() use internally. Routing every Set-Cookie write through one tiny, dependency-free implementation means there is exactly one place that knows the RFC 6265bis attribute rules, the __Host- / __Secure- prefix rules, and the secure-by-default posture. Reach for these when you need a custom cookie outside the built-in session and CSRF flows.

Secure by default

When you omit attributes, the helpers choose the safest interpretation:

  • secure: true
  • httpOnly: true
  • sameSite: "Strict"
  • path: "/"

Writing a cookie

serializeCookie(name, value, attributes?) returns a single Set-Cookie header value. The value is URI-encoded so binary signature bytes and base64 padding round-trip safely. Set it on your response headers.

ts
import { App, serializeCookie } from "@daloyjs/core";

const app = new App();

app.route({
  method: "POST",
  path: "/prefs",
  operationId: "savePrefs",
  responses: { 204: { description: "saved" } },
  handler: async () => ({
    status: 204 as const,
    body: undefined,
    headers: {
      "set-cookie": serializeCookie("__Host-theme", "dark", {
        maxAgeSeconds: 60 * 60 * 24 * 365,
      }),
    },
  }),
});

The __Host- prefix is the strongest anti-cookie-tossing choice: the browser only accepts it when it is Secure, has Path=/, and carries no Domain. The helper enforces exactly those constraints, so a misconfiguration throws instead of silently shipping a cookie the browser drops.

Reading a cookie

readRequestCookie(header, name) parses a single cookie out of the request Cookie header, returning null when it is absent. It also includes a cookie-tossing defense: if the same name appears more than once, it returns null rather than guessing which copy is authentic, forcing a re-authentication instead of letting an attacker-injected shadow cookie win.

ts
import { readRequestCookie } from "@daloyjs/core";

app.route({
  method: "GET",
  path: "/prefs",
  operationId: "getPrefs",
  responses: { 200: { description: "ok" } },
  handler: async ({ request }) => {
    const theme = readRequestCookie(request.headers.get("cookie"), "__Host-theme");
    return { status: 200 as const, body: { theme: theme ?? "light" } };
  },
});

Clearing a cookie

serializeClearCookie(name, attributes?) emits a Set-Cookie value with Max-Age=0, preserving the original attributes so intermediaries match the cookie they are meant to delete.

ts
import { serializeClearCookie } from "@daloyjs/core";

return {
  status: 204 as const,
  body: undefined,
  headers: { "set-cookie": serializeClearCookie("__Host-theme") },
};

Validating attributes up front

assertCookieAttributes() throws on the first RFC 6265bis or secure-default violation. serializeCookie() and serializeClearCookie() call it for you, but you can invoke it directly to validate a configuration at construction time — failing the boot rather than shipping a cookie the browser would silently drop.

ts
import { assertCookieAttributes } from "@daloyjs/core";

assertCookieAttributes({
  scope: "my-feature",
  name: "__Secure-flags",
  attributes: { secure: true, sameSite: "Lax" },
  isProduction: process.env.NODE_ENV === "production",
});

Common refusals: sameSite: "None" without secure: true; a __Host- cookie with a Domain or a non-root path; and (in production) a __Secure- cookie without secure: true.

For client-readable tokens such as a CSRF mirror, set httpOnly: false explicitly — everything else stays locked down.

When to use the built-ins instead

For authenticated sessions reach for session(), and for CSRF protection reach for csrf(). They already route through these helpers with hardened defaults. Use the raw helpers for everything else: preferences, feature flags, consent banners, and other small bits of per-client state.