Session Management
Sessions store user data across requests using signed cookies.
Session Middleware
The simplest way to configure sessions is via createSvelarApp:
import { createSvelarApp } from '@beeblock/svelar/hooks';
import { DatabaseSessionStore } from '@beeblock/svelar/session';
export const { handle, handleError } = createSvelarApp({
auth,
sessionStore: new DatabaseSessionStore(), // persistent sessions
sessionLifetime: 60 * 60 * 24, // idle lifetime
sessionIdleLifetime: 60 * 60, // optional distinct authenticated idle timeout
sessionAbsoluteLifetime: 60 * 60 * 24 * 30, // hard maximum age
sessionTouchOnActivity: true, // do not renew on polling/SSE
sessionTouchThrottle: 300, // touch at most every 5 minutes
});
For manual setup with createSvelarHooks:
import { SessionMiddleware, DatabaseSessionStore } from '@beeblock/svelar/session';
export const handle = createSvelarHooks({
middleware: [
new SessionMiddleware({
store: new DatabaseSessionStore(), // requires the sessions migration
secret: process.env.APP_KEY!,
lifetime: 60 * 60 * 24, // 24 hours
absoluteLifetime: 60 * 60 * 24 * 30,
touchOnActivity: true,
touchThrottle: 300,
cookieName: 'svelar_session', // Cookie name
}),
],
});
Sliding Sessions
Set touchOnActivity: true when you want sessions to slide based on real user activity. Svelar renews the store TTL for mutations and document navigations, but ignores SSE (Accept: text/event-stream) and polling-style requests. X-Svelar-Session-Activity: false can opt out; a client-supplied true cannot turn polling into human activity. For a trusted relevant server-side data load, call event.locals.session.markActivity(true), or supply the manual middleware's activity(ctx) callback. Status/polling endpoints should call markActivity(false).
Use absoluteLifetime for a hard cap that activity cannot extend. touchThrottle prevents every click or tab from writing the session store; the default is 300 seconds.
Activity policies require atomic compareAndSwap support in the session store. All built-in stores implement it; custom stores must implement it too. Concurrent requests merge their own changes rather than resurrecting a logged-out session or overwriting another tab. An expired idle/absolute session is destroyed, not renewed by polling or a late response. Regular cookies are session cookies unless configured otherwise; remembered logins intentionally get a bounded persistent cookie.
Remember-Me And Expiry Warning
Configure sessionRememberLifetime and sessionRememberAbsoluteLifetime in createSvelarApp, or rememberLifetime/rememberAbsoluteLifetime in manual SessionMiddleware. Remember-me is unavailable unless configured. Login uses auth.attempt(credentials, session, { remember: true }); it rotates the session ID and resets timing from successful authentication. Absolute expiry still requires signing in again. Logout removes remember state and invalidates the stored session.
New apps ship a shared Zod/Valibot boolean remember field, inline Superforms validation, password visibility control, disabled/spinning submit state and an authenticated expiry warning. Set SESSION_REMEMBER_LIFETIME=0 to hide/disable persistent sign-in. Session defaults are configurable in .env; regular and remembered idle/absolute limits are independent.
The generated /api/auth/session route binds SessionActivityController: GET returns { data: SessionActivityStatus, meta } without touching the session; POST explicitly renews a still-valid authenticated session and requires same-origin CSRF. It does not extend absolute expiry. Unknown, forged or expired sessions return 401, not an anonymous renewal. Custom cookie/header names must be supplied to the controller and UI consistently.
SessionWarning from @beeblock/svelar/ui provides an accessible expiry dialog and a retryable renewal action. Its multitab bus only invalidates state: a tab cannot extend another tab's deadline by broadcasting a message. Only authenticated server responses update timing, with server-clock offset handling. It checks on visibility/warning boundaries, not continuous keep-alive polling. Mount it with the server's session.getActivityStatus() and the configured status/renew URLs; use SessionActivityClient directly for a custom presentation.
Using Sessions
// Set session data
event.locals.session.set('auth_user_id', 1);
event.locals.session.set('preferences', { theme: 'dark' });
// Get session data
const userId = event.locals.session.get('auth_user_id');
const preferences = event.locals.session.get('preferences');
// Remove from session
event.locals.session.forget('auth_user_id');
// Regenerate ID (for security after login)
event.locals.session.regenerateId();
// Get all session data
const all = event.locals.session.all();
// Check if key exists
const hasTheme = event.locals.session.has('preferences.theme');
// Get with default
const theme = event.locals.session.get('preferences.theme', 'light');
Session Stores
MemorySessionStore (development):
import { MemorySessionStore } from '@beeblock/svelar/session';
new SessionMiddleware({
store: new MemorySessionStore(),
});
DatabaseSessionStore (production — survives server restarts):
import { DatabaseSessionStore } from '@beeblock/svelar/session';
new SessionMiddleware({
store: new DatabaseSessionStore('sessions'),
});
The sessions table is managed by Svelar core migrations. Works with SQLite, PostgreSQL, and MySQL.
FileSessionStore (simple persistent storage, no database needed):
import { FileSessionStore } from '@beeblock/svelar/session';
new SessionMiddleware({
store: new FileSessionStore(), // defaults to storage/sessions/
});
// Or specify a custom directory
new SessionMiddleware({
store: new FileSessionStore('/tmp/my-app-sessions'),
});
Each session is stored as a JSON file. Expired sessions are cleaned up on gc(). Good for single-server deployments without a database.
RedisSessionStore (high-performance, multi-server):
import { RedisSessionStore } from '@beeblock/svelar/session';
// Auto-connect to localhost:6379
new SessionMiddleware({
store: new RedisSessionStore(),
});
// Connect with a URL
new SessionMiddleware({
store: new RedisSessionStore({ url: 'redis://user:pass@redis-host:6379' }),
});
// Bring your own ioredis client
import Redis from 'ioredis';
const redis = new Redis({ host: 'redis-host', port: 6379 });
new SessionMiddleware({
store: new RedisSessionStore({ client: redis, prefix: 'myapp:session:' }),
});
Requires the ioredis package (npm install ioredis). Redis handles expiration natively via TTL — no garbage collection needed. Ideal for multi-server/load-balanced deployments.
Tip: Use
MemorySessionStoreonly for development. For production, choose based on your infrastructure:
- DatabaseSessionStore — already using a database, simplest setup
- FileSessionStore — single server, no database needed
- RedisSessionStore — multi-server, best performance