Skip to content

Cookies

Cookies persist state across requests — the obvious case being session auth, but also CSRF tokens, feature flags, and partner integrations. Slingshot ships signed-cookie helpers and a small set of framework cookie names you’ll see in any auth-enabled app.

  • Set and read cookies in any handler via Hono’s cookie helpers
  • Sign cookie values for tamper detection (HMAC-SHA256, no encryption)
  • Read framework-issued cookies by their canonical names
  • Opt into framework CSRF cookie handling via security.csrf.enabled

Slingshot uses Hono’s request context, so reach for setCookie / getCookie from hono/cookie:

src/preferences/package.ts
// @skip-typecheck
import { getCookie, setCookie } from 'hono/cookie';
import { definePackage, domain, route } from '@lastshotlabs/slingshot';
export const preferencesPackage = definePackage({
name: 'preferences',
domains: [
domain({
name: 'theme',
basePath: '/theme',
routes: [
route.post({
path: '/',
handler: ({ c, respond }) => {
setCookie(c, 'theme', 'dark', {
httpOnly: true,
secure: true,
sameSite: 'Lax',
maxAge: 60 * 60 * 24 * 365,
});
return respond.json({ ok: true });
},
}),
route.get({
path: '/',
handler: ({ c, respond }) => respond.json({ theme: getCookie(c, 'theme') ?? 'light' }),
}),
],
}),
],
});

Always set httpOnly for cookies the client doesn’t need to read. secure: true restricts the cookie to HTTPS — required for SameSite=None cross-site flows.

When a cookie value carries security meaning — a remember-me token, a session ID, a feature flag a user shouldn’t flip themselves — sign it. signCookieValue wraps the value with an HMAC signature; verifyCookieValue returns the original value or null on tamper.

// @skip-typecheck
import { getCookie, setCookie } from 'hono/cookie';
import { signCookieValue, verifyCookieValue } from '@lastshotlabs/slingshot';
const secret = process.env.COOKIE_SECRET!;
setCookie(c, 'pref-set', signCookieValue('experiment-b', secret), {
httpOnly: true,
secure: true,
});
const raw = getCookie(c, 'pref-set');
const value = raw ? verifyCookieValue(raw, secret) : null;
// value === 'experiment-b' if untampered, or null if signature is invalid

Signed cookies are not encrypted — anyone with the cookie can read the original value. Use them when you need integrity, not confidentiality.

The auth plugin sets a fixed set of cookies. The names are exported as constants so you can reference them in middleware, tests, and clients without typos:

ConstantValuePurpose
COOKIE_TOKENtokenSession access token (HttpOnly)
COOKIE_REFRESH_TOKENrefresh_tokenLong-lived refresh token (HttpOnly)
COOKIE_CSRF_TOKENcsrf_tokenCSRF synchronizer token (readable by JS)
HEADER_USER_TOKENx-user-tokenHeader alternative to the access cookie
HEADER_REFRESH_TOKENx-refresh-tokenHeader alternative to refresh cookie
HEADER_CSRF_TOKENx-csrf-tokenCSRF token submitted by the client
// @skip-typecheck
import { getCookie } from 'hono/cookie';
import { COOKIE_TOKEN, HEADER_CSRF_TOKEN } from '@lastshotlabs/slingshot';
const session = getCookie(c, COOKIE_TOKEN);
const csrf = c.req.header(HEADER_CSRF_TOKEN);

Cookie-authenticated routes are vulnerable to CSRF unless you check a synchronizer token. Enable it once in app config — the framework sets csrf_token, validates x-csrf-token on state-changing requests, and exempts OAuth callbacks automatically:

app.config.ts
import { defineApp } from '@lastshotlabs/slingshot';
export default defineApp({
meta: { name: 'my-app', version: '1.0.0' },
security: {
csrf: {
enabled: true,
checkOrigin: true,
exemptPaths: ['/webhooks/*'],
},
},
});