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
Sessions are stored in a KV store. By default SessionConfig uses the in-memory adaptor under the session: namespace. To customize the namespace (or other options), extend SessionConfig:
@Config
class MySessionConfig extends SessionConfig {
override readonly kvStoreNamespace = 'sessions:';
}
3. Register Middleware
const app = createApp([http({
controllers: [AuthController, UserController],
middlewares: [SessionMiddleware],
})], { configs: [MySessionConfig] });
4. Manage Sessions
Use session functions in your handlers:
@Controller('/auth')
class AuthController {
@Post('/login')
async login(body = validated(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
| Function | Description |
|---|---|
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:
@Config
class MySessionConfig extends SessionConfig {
override readonly kvStoreNamespace = '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
| Option | Type | Default | Description |
|---|---|---|---|
kv | KVAdaptor | MemoryKV | KV adaptor (constructor arg 2) backing session storage |
kvStoreNamespace | string | 'session:' | Namespace prefix for session keys |
secret | string | env.getString('SESSION_SECRET') | Secret for signing session IDs |
cookieName | string | 'session' | Cookie name |
ttlSec | number | 86400 (1 day) | Session TTL in seconds |
cookieOptions | object | See below | Cookie configuration |
Default Cookie Options
override get cookieOptions() {
return {
httpOnly: true,
secure: this.env.getString('NODE_ENV', '') === 'production',
sameSite: 'Lax' as const,
path: '/',
};
}
}
Storage Backends
Memory (Development)
@Config
class MySessionConfig extends SessionConfig {
constructor(kv = inject(MemoryKV)) {
super(undefined, kv);
}
}
Redis (Production)
SessionConfig takes the KV adaptor as its second constructor argument. Pass a RedisKVAdaptor to super() to store sessions in Redis (leave the first argument as undefined to keep the default Env injection):
@Config
class MySessionConfig extends SessionConfig {
constructor(kv = inject(RedisKVAdaptor)) {
super(undefined, kv);
}
override readonly kvStoreNamespace = 'sessions:';
}
Using Redis requires registering RedisConfig (from @zeltjs/redis) so the adaptor can resolve its connection.
Integration with User Context
Sessions don't automatically set the user context. Add middleware to bridge them:
@Middleware
export class SessionAuthMiddleware {
constructor(private userRepo = inject(UserRepository)) {}
async use(c: RequestContext, next: Next): Promise<Response | undefined> {
const session = getSession() as { userId?: string } | undefined;
if (session?.userId) {
const user = await this.userRepo.findById(session.userId);
setUser(
{ id: user.id, name: user.name, email: user.email },
user.roles
);
}
await next();
return undefined;
}
}
Register after SessionMiddleware:
const app = createApp([http({
controllers: [UserController],
middlewares: [SessionMiddleware, SessionAuthMiddleware],
})], { configs: [MySessionConfig] });
Security Considerations
CSRF Protection
Session-based authentication requires CSRF protection. Consider using:
SameSite=Strictcookies (strongest, may affect UX)SameSite=Laxcookies with CSRF tokens for mutations- Double-submit cookie pattern
Session Fixation
Always regenerate the session ID after login:
@Controller('/auth')
class AuthController {
@Post('/login')
async login(body = validated(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:
const cookieOptions = {
httpOnly: true,
secure: true, // HTTPS only
sameSite: 'Strict' as const,
path: '/',
};