Skip to main content

Session Authentication

@zeltjs/auth-session provides cookie-based session management for server-rendered applications.

Installation

pnpm add @zeltjs/auth-session @zeltjs/kv

Quick Start

1. Set the Secret

Set the SESSION_SECRET environment variable:

# .env
SESSION_SECRET=your-secret-key-at-least-32-characters

2. Configure Session Store

Create a custom config that provides a KV store for session data:

import { Config, inject } from '@zeltjs/core';
import { MemoryKVService } from '@zeltjs/kv';
import { SessionConfig } from '@zeltjs/auth-session';

@Config
class MySessionConfig extends SessionConfig {
private kv = inject(MemoryKVService);

override get store() {
return this.kv.namespace('sessions');
}
}

3. Register Middleware

import { createApp } from '@zeltjs/core';
import { MemoryKVService } from '@zeltjs/kv';
import { SessionMiddleware } from '@zeltjs/auth-session';

const app = createApp({
http: {
controllers: [AuthController, UserController],
middlewares: [SessionMiddleware],
},
configs: [MySessionConfig],
injectables: [MemoryKVService],
});

4. Manage Sessions

Use session functions in your handlers:

import { Controller, Post, Get, bodyParam } from '@zeltjs/core';
import { getSession, setSession, destroySession } from '@zeltjs/auth-session';

@Controller('/auth')
class AuthController {
@Post('/login')
async login(body = bodyParam(LoginSchema)) {
const user = await validateCredentials(body.email, body.password);
if (!user) {
throw new HTTPException(401, { message: 'Invalid credentials' });
}

setSession({ userId: user.id, name: user.name });
return { success: true };
}

@Get('/me')
me() {
const session = getSession();
if (!session) {
throw new HTTPException(401, { message: 'Not logged in' });
}
return session;
}

@Post('/logout')
logout() {
destroySession();
return { success: true };
}
}

Session API

FunctionDescription
getSession()Get current session data (undefined if not logged in)
setSession(data)Set session data (replaces existing)
updateSession(fn)Update session data with a function
destroySession()Destroy session and clear cookie
isNewSession()Check if this is a newly created session
getSessionId()Get the current session ID

setSession

Create or replace the session:

setSession({
userId: '123',
name: 'Alice',
cart: [{ productId: 'abc', qty: 2 }],
});

updateSession

Partially update the session:

updateSession((session) => ({
...session,
lastActivity: Date.now(),
}));

destroySession

Clear the session and cookie (for logout):

destroySession();

Type-Safe Sessions

Extend SessionSchema for type-safe session access:

declare module '@zeltjs/auth-session' {
interface SessionSchema {
userId?: string;
name?: string;
email?: string;
cart?: CartItem[];
}
}

Now all session functions are typed:

const session = getSession();
// TypeScript knows: session?.userId, session?.name, session?.cart

setSession({ userId: '123', name: 'Alice' });
// Type-checked against SessionSchema

Configuration

Extend SessionConfig to customize behavior:

import { Config, inject } from '@zeltjs/core';
import { RedisKVService } from '@zeltjs/kv-redis';
import { SessionConfig } from '@zeltjs/auth-session';

@Config
class MySessionConfig extends SessionConfig {
private kv = inject(RedisKVService);

override get store() {
return this.kv.namespace('sessions');
}

override get cookieName(): string {
return 'my_session'; // default: 'session'
}

override get ttlSec(): number {
return 86400 * 7; // 7 days (default: 1 day)
}

override get cookieOptions() {
return {
httpOnly: true,
secure: true,
sameSite: 'Strict' as const,
path: '/',
};
}
}

Configuration Options

OptionTypeDefaultDescription
storeKVNamespaceRequiredKV namespace for session storage
secretstringprocess.env.SESSION_SECRETSecret for signing session IDs
cookieNamestring'session'Cookie name
ttlSecnumber86400 (1 day)Session TTL in seconds
cookieOptionsobjectSee belowCookie configuration
{
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'Lax',
path: '/',
}

Storage Backends

Memory (Development)

import { MemoryKVService } from '@zeltjs/kv';

@Config
class MySessionConfig extends SessionConfig {
private kv = inject(MemoryKVService);
override get store() {
return this.kv.namespace('sessions');
}
}

Redis (Production)

import { RedisKVService } from '@zeltjs/kv-redis';

@Config
class MySessionConfig extends SessionConfig {
private kv = inject(RedisKVService);
override get store() {
return this.kv.namespace('sessions');
}
}

Cloudflare KV

import { CloudflareKVService } from '@zeltjs/kv-cloudflare';

@Config
class MySessionConfig extends SessionConfig {
private kv = inject(CloudflareKVService);
override get store() {
return this.kv.namespace('sessions');
}
}

Integration with User Context

Sessions don't automatically set the user context. Add middleware to bridge them:

import type { FunctionMiddleware } from '@zeltjs/core';
import { setUser } from '@zeltjs/core';
import { getSession } from '@zeltjs/auth-session';

export const sessionAuthMiddleware: FunctionMiddleware = async (c, next) => {
const session = getSession();

if (session?.userId) {
const user = await db.users.findById(session.userId);
setUser(
{ id: user.id, name: user.name, email: user.email },
user.roles
);
}

await next();
};

Register after SessionMiddleware:

const app = createApp({
http: {
controllers: [UserController],
middlewares: [SessionMiddleware, sessionAuthMiddleware],
},
configs: [MySessionConfig],
injectables: [MemoryKVService],
});

Security Considerations

CSRF Protection

Session-based authentication requires CSRF protection. Consider using:

  • SameSite=Strict cookies (strongest, may affect UX)
  • SameSite=Lax cookies with CSRF tokens for mutations
  • Double-submit cookie pattern

Session Fixation

Always regenerate the session ID after login:

@Post('/login')
async login(body = bodyParam(LoginSchema)) {
const user = await validateCredentials(body.email, body.password);

destroySession(); // Clear old session
setSession({ userId: user.id, name: user.name }); // Creates new ID

return { success: true };
}

Secure Cookies

In production, always use secure cookies:

override get cookieOptions() {
return {
httpOnly: true,
secure: true, // HTTPS only
sameSite: 'Strict' as const,
path: '/',
};
}