js/net/http/session

js/net/http/session.ts

internal:net/http/session — revision-safe HTTP app session implementation.

The public API is exported only by fino:net/http/app. Callers supply a an atomic, expiry-capable fino:store backend directly; session lifecycle code owns record validation, TTL translation, sealed identifiers, and conditional writes.

Session data uses the supplied provider's value representation and must be structured-cloneable as well as supported by that provider. It is suitable for authentication identity and small request-scoped metadata, not as a transactional application database. A distributed adapter must provide atomic per-key conditional writes and read-after-write behavior to preserve the security guarantees of invalidation and regeneration; eventual replication alone is insufficient for immediate global logout.

import { memoryStore } from 'fino:store';
import { sessions } from 'fino:net/http/app';

const middleware = sessions({
  store: memoryStore(),
  keys: [{ id: 'primary', secret: process.env.SESSION_SECRET! }],
  ttlMs: 3_600_000,
});

Interfaces

interface SessionClock {

Clock used for session timestamps and expiry checks.

Methods

now(): number

Return the current Unix timestamp in milliseconds.

interface SessionRecord<T = Record<string, unknown>> {

Durable, backend-neutral representation of one server session.

Properties

id: string

Opaque session identifier stored only inside the sealed browser cookie.

data: T

Structured-cloneable application data accepted by the configured store.

createdAt: number

Unix timestamp in milliseconds when this session was first created.

updatedAt: number

Unix timestamp in milliseconds for the last application-data update.

expiresAt: number

Absolute Unix timestamp in milliseconds after which the record is invalid.

interface SessionKey {

One cookie-sealing key accepted by server-session middleware.

Properties

id: string

Stable short identifier written outside the sealed cookie payload.

secret: BufferLike

Secret material used by AES-256-GCM cookie sealing.

interface Session<T = Record<string, unknown>> extends SessionRecord<T> {

Request-local session exposed to HTTP application handlers.

Properties

isNew: boolean

Whether this request created or regenerated the session.

Methods

regenerate(): void

Replace the session ID while retaining its data.

Call this after authentication succeeds to prevent session fixation. The old ID is deleted before the replacement is committed.

invalidate(): void

Delete the stored session and expire its browser cookie after the response.

interface SessionOptions<T = Record<string, unknown>> {

Options for the secure HTTP session producer.

Properties

store: AtomicExpiringStore

Caller-owned generic store with atomic commit and provider-managed expiry.

keys: readonly [SessionKey, ...SessionKey[]]

Sealing keys ordered primary-first; old keys remain readable for rotation.

ttlMs: number

Session lifetime in milliseconds. Must be finite and greater than zero.

cookie?: string

Cookie name. Defaults to fino.sid.

cookieOptions?: CookieOptions

Additional cookie policy merged over secure defaults.

rolling?: boolean

Extend expiry after each successful request. Defaults to false.

clock?: SessionClock

Clock used for deterministic timestamps and expiry.

Classes

class SessionConflictError extends Error {

Error raised when a request tries to commit data based on a stale revision.

The middleware never guesses how to merge arbitrary session data or reruns a handler whose side effects may already have happened. Applications may turn this error into a conflict response or ask the client to retry safely.

Readonly Properties

readonly sessionId: string

Conflicting session identifier.

Constructors

constructor(sessionId: string)

Create a conflict error for sessionId.

Functions

function sessions<T = Record<string, unknown>>(options: SessionOptions<T>): Producer

Create an HTTP app producer that loads and commits a secure server session.

Install it with .value('session', sessions(options)), normally after the app's cookies() producer. The cookie contains only an AES-GCM-sealed session ID. New cookies use the first key; cookies opened with a later key are automatically resealed with the primary key after a successful request.

Unchanged fixed-expiry sessions perform no store write. Rolling sessions conditionally extend expiry. Concurrent expiry-only updates may retry against the latest record, while conflicting application-data mutations raise SessionConflictError rather than lose an update.

For OAuth/OIDC callbacks, capture the request session in oauthCallback({ onSuccess }), call session.regenerate() after the token exchange succeeds, and copy only the verified identity claims the application needs into session.data. Provider access and refresh tokens are not stored automatically.

import { memoryStore } from 'fino:store';
import { App, cookies, sessions } from 'fino:net/http/app';

const app = new App();
const authenticated = app.value('cookies', cookies()).value('session', sessions({
  store: memoryStore(),
  keys: [{ id: '2026-07', secret: process.env.SESSION_SECRET! }],
  ttlMs: 24 * 60 * 60_000,
}));
authenticated.get('/me').handle((ctx) => Response.json(ctx.session.data));