GioJSdocs
On this page

createSessionStorage

Create encrypted cookie sessions: the data lives in an AES-256-GCM encrypted, signed cookie that Rust guards can verify.

lib/session.server.ts
import { createSessionStorage } from '@gio.js/core';

interface UserSession {
  userId: string;
  flash: string;
}

export const sessions = createSessionStorage<UserSession>();

Reference

createSessionStorage<Data>(options?). Data is the shape of the session (default Record<string, unknown>); its values must be JSON-serializable.

OptionTypeDefaultDescription
cookieNamestring'gio_session'The cookie that holds the session - also the one require_session guards read unless they name another. Must be a valid RFC 6265 cookie name.
secretsstring | string[]GIO_SESSION_SECRETThe keys. The first encrypts and signs; all of them are accepted when reading. Each must be at least 32 bytes. Leave it unset for a storage a guard checks: guards verify with GIO_SESSION_SECRET only.
maxAgenumber604800Lifetime in whole seconds (7 days), written into the token and into the cookie's Max-Age. Must be a positive integer.
cookieOmit<CookieOptions, 'maxAge' | 'expires'>{}Cookie attributes, with the defaults of serializeCookie: HttpOnly, SameSite=Lax, Path=/, Secure in production.

Returns

A SessionStorage<Data>:

FieldTypeDefaultDescription
cookieNamestring-The cookie name in use.
getSession(source)Session<Data>-Reads the request's session. source is a getServerSideProps context, a route.ts or action request, a GioSocket, a plugin's IPCRequest, a web Request, or the raw Cookie header. A missing, expired or tampered cookie gives an empty session with isNew: true - it never throws.
commitSession(session, options?)string-Encrypts the session into a Set-Cookie value with a fresh expiry. options.maxAge overrides the lifetime for this cookie. Throws when the cookie would exceed 4096 bytes.
destroySession()string-A Set-Cookie value that deletes the session cookie.

Session

FieldTypeDefaultDescription
isNewboolean-True when the request carried no valid session.
dataPartial<Data>-A shallow copy of the current values.
get(key)Data[K] | undefined-One value.
set(key, value)void-Sets a value; undefined removes the key.
unset(key)void-Removes a key.
has(key)boolean-Whether the key is set.

Changes live only in the Session object until you send commitSession(session) as a Set-Cookie header.

Secrets and modes

  • GIO_SESSION_SECRET holds one secret or several, comma-separated. Each is at least 32 bytes; generate one with node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))".
  • Production refuses to create a storage without a secret: the call throws GIO_SESSION_SECRET is not set.... A module that creates its storage at the top level therefore fails to load, and a route.ts that imports it answers 500.
  • Development (NODE_ENV=development) without a secret uses an ephemeral one - generated by the dev server, which also hands it to require_session guards, or by the worker process itself when it runs without one - and logs a warning: sessions reset when it restarts.
  • A secrets option whose first secret is not in a set GIO_SESSION_SECRET logs a warning, since a guard on that cookie would reject every session.

Errors

Bad configuration throws at creation, not on the first login: a missing secret in production, a secret under 32 bytes, an invalid cookieName, a maxAge that is not a positive integer, and cookie options browsers would reject (for example sameSite: 'none' with secure: false).

Examples

Log in

app/api/login/route.ts
import type { GioRequest } from '@gio.js/core';
import { sessions } from '../../../lib/session.server.ts';

export async function POST(req: GioRequest) {
  const form = await req.formData();
  const user = await verifyPassword(String(form.get('email')), String(form.get('password')));
  if (user === null) return new Response('Wrong email or password', { status: 401 });

  const session = sessions.getSession(req);
  session.set('userId', user.id);
  return new Response(null, {
    status: 303,
    headers: { Location: '/dashboard', 'Set-Cookie': sessions.commitSession(session) },
  });
}

Read it in a page

app/dashboard/page.tsx
import { redirect, type GetServerSideProps } from '@gio.js/core';
import { sessions } from '../../lib/session.server.ts';

export const getServerSideProps = (async (ctx) => {
  const userId = sessions.getSession(ctx).get('userId');
  if (userId === undefined) throw redirect('/login');
  return { props: { user: await db.users.find(userId) } };
}) satisfies GetServerSideProps;

Log out

app/api/logout/route.ts
import { sessions } from '../../../lib/session.server.ts';

export function POST() {
  return new Response(null, {
    status: 303,
    headers: { Location: '/', 'Set-Cookie': sessions.destroySession() },
  });
}

Protect a section in Rust

A guard checks the session cookie's signature and expiry before any Node code runs, with GIO_SESSION_SECRET:

gio.toml
[[guards]]
path = "/dashboard/*rest"
require_session = true
redirect_to = "/login"

Good to know

  • Reading a session makes the page personal. Reading ctx.cookies (which getSession(ctx) does) keeps the render out of the shared page cache.
  • Stateless. Logging out deletes the browser's cookie, but a copied cookie stays valid until it expires. Keep maxAge short, or store a per-user session version server-side and compare it, when logout must end every copy.
  • Not rolling. The expiry is set when you commit. To extend a session on activity, commit it again.
  • Size. Keep ids and small flags in the session; the whole cookie must stay under 4096 bytes.
  • The cookie name is bound into the token. Changing cookieName logs everyone out, and a token cannot be replayed under another cookie that shares the secret.
  • Rotation. Prepend a new secret (GIO_SESSION_SECRET=new,old), restart, and remove the old one after maxAge. Removing a secret invalidates every session it signed at once.
  • Tests. renderPage and callRoute set a random secret for the test process when none is configured, so session modules load there.

Version history

VersionChanges
v0.1.0-beta.8Introduced.