@analog-tools/session
Version:
Session management for AnalogJS server-side applications
388 lines (293 loc) • 10.4 kB
Markdown
> **✨ Simplified Session Management** ✨
> Completely redesigned with a clean, functional API. No more over-engineered abstractions!
A simple, performant session management library for H3-based applications (Nuxt, Nitro, AnalogJS). Designed for simplicity and efficiency with a single API pattern.
[](https://www.npmjs.com/package/@analog-tools/session)
[](https://opensource.org/licenses/MIT)
[](https://bundlephobia.com/package/@analog-tools/session)
## Table of Contents
- [Features](#features)
- [Breaking Changes in v0.0.6](#breaking-changes-in-v006)
- [Installation](#installation)
- [Quick Start](#quick-start)
- [API Reference](#api-reference)
- [Usage Examples](#usage-examples)
- [Configuration](#configuration)
- [Migration from v0.0.5](#migration-from-v005)
- [Performance](#performance)
- [Security](#security)
- [Contributing](#contributing)
## Features
- 🎯 **Simple**: Single functional API, no dual patterns or classes
- ⚡ **Performance**: ~4KB gzipped, optimized for modern applications
- 🔒 **Secure**: Essential crypto with timing attack resistance
- 🔄 **Direct**: Uses unstorage directly, no wrapper abstractions
- 🔄 **Rotation**: Secret key rotation support
- 🧩 **TypeScript**: Full type safety with minimal generics
- ⚡ **Modern**: Built for AnalogJS
## Breaking Changes in v0.0.6
This version introduces a **complete API redesign** that simplifies the previous over-engineered approach:
- ❌ **Removed**: `Session` class and `SessionHandler` interface
- ❌ **Removed**: `UnstorageSessionStore` wrapper and `registerStorage` factory
- ❌ **Removed**: Complex crypto module (309 lines → 96 lines)
- ❌ **Removed**: Dual API patterns and unnecessary abstractions
- ✅ **Added**: Simple functional API with direct storage integration
- ✅ **Added**: Essential crypto functions only
- ✅ **Added**: Storage factory functions
**Migration Guide**: See [Migration from v0.0.5](#migration-from-v005) section below.
## Installation
```bash
npm install @analog-tools/session
```
## Quick Start
### Basic Usage with Memory Storage
```typescript
import { defineEventHandler } from 'h3';
import { useSession, getSession, updateSession, createMemoryStore } from '@analog-tools/session';
const store = createMemoryStore();
export default defineEventHandler(async (event) => {
// Initialize session middleware
await useSession(event, {
store,
secret: 'your-secret-key',
maxAge: 86400, // 24 hours
});
// Get current session data
const session = getSession(event);
console.log('Current session:', session);
// Update session data
await updateSession(event, (data) => ({
visits: (data.visits || 0) + 1,
lastAccess: Date.now(),
}));
return {
visits: getSession(event)?.visits || 0,
};
});
```
```typescript
import { createRedisStore } from '@analog-tools/session';
const store = createRedisStore({
host: 'localhost',
port: 6379,
// Optional: password, db, etc.
});
export default defineEventHandler(async (event) => {
await useSession(event, {
store,
secret: ['new-secret', 'old-secret'], // Supports rotation
name: 'my-app-session',
maxAge: 3600,
cookie: {
secure: true,
httpOnly: true,
sameSite: 'strict',
},
});
// Your session logic here
});
```
Initialize session middleware for an H3 event. Must be called before other session operations.
```typescript
await useSession(event, {
store: Storage<T>, // Direct unstorage Storage instance
secret: string | string[], // Secret(s) for signing cookies
name?: string, // Cookie name (default: 'connect.sid')
maxAge?: number, // TTL in seconds (default: 86400)
cookie?: CookieOptions, // Standard cookie options
generate?: () => T, // Optional initial data generator
});
```
Get current session data from the event context.
```typescript
const session = getSession<{ userId?: string }>(event);
if (session?.userId) {
console.log('User ID:', session.userId);
}
```
Update session data immutably and persist to storage.
```typescript
await updateSession(event, (currentData) => ({
lastLogin: new Date().toISOString(),
loginCount: (currentData.loginCount || 0) + 1,
}));
```
Destroy the current session, clear storage and cookies.
```typescript
await destroySession(event);
```
Regenerate session ID while preserving data (useful after login).
```typescript
await regenerateSession(event);
```
Create in-memory storage for development and testing.
```typescript
const store = createMemoryStore();
```
Create Redis-backed storage for production.
```typescript
const store = createRedisStore({
url: 'redis://localhost:6379',
// or individual options:
host: 'localhost',
port: 6379,
password: 'optional',
db: 0,
});
```
Sign a cookie value with HMAC-SHA256.
Verify and unsign a cookie value, supports multiple secrets for rotation.
```typescript
import { defineEventHandler, readBody, createError } from 'h3';
import {
useSession,
getSession,
updateSession,
destroySession,
regenerateSession,
createRedisStore
} from '@analog-tools/session';
// Session configuration (define once, reuse across routes)
const sessionConfig = {
store: createRedisStore({ url: process.env.REDIS_URL }),
secret: process.env.SESSION_SECRET!,
maxAge: 3600, // 1 hour
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
sameSite: 'strict' as const,
},
};
// Login endpoint
export default defineEventHandler(async (event) => {
await useSession(event, sessionConfig);
const { username, password } = await readBody(event);
const user = await validateUser(username, password);
if (user) {
// Regenerate session ID for security
await regenerateSession(event);
// Store user data
await updateSession(event, () => ({
userId: user.id,
username: user.username,
loginTime: Date.now(),
}));
return { success: true };
}
return { success: false };
});
// Protected endpoint
export default defineEventHandler(async (event) => {
await useSession(event, sessionConfig);
const session = getSession(event);
if (!session?.userId) {
throw createError({
statusCode: 401,
statusMessage: 'Not authenticated',
});
}
return { user: session };
});
// Logout endpoint
export default defineEventHandler(async (event) => {
await useSession(event, sessionConfig);
await destroySession(event);
return { success: true };
});
```
```typescript
interface UserSession {
userId?: string;
username?: string;
roles?: string[];
preferences?: Record<string, unknown>;
lastActivity?: number;
}
export default defineEventHandler(async (event) => {
await useSession<UserSession>(event, {
store: createRedisStore({ url: process.env.REDIS_URL }),
secret: process.env.SESSION_SECRET!,
generate: () => ({ lastActivity: Date.now() }),
});
const session = getSession<UserSession>(event);
// TypeScript knows session has UserSession shape
});
```
```typescript
interface SessionConfig<T> {
store: Storage<T>; // Direct unstorage Storage
secret: string | string[]; // Support for key rotation
name?: string; // Cookie name (default: 'connect.sid')
maxAge?: number; // TTL in seconds (default: 86400)
cookie?: CookieOptions; // Cookie configuration
generate?: () => T; // Initial session data generator
}
```
```typescript
interface CookieOptions {
domain?: string;
path?: string; // Default: '/'
secure?: boolean; // Default: false
httpOnly?: boolean; // Default: true
sameSite?: boolean | 'lax' | 'strict' | 'none'; // Default: 'lax'
}
```
If you're upgrading from v0.0.5 or earlier, here's how to migrate your code:
```typescript
// If you were using the old Session class (not available in any released version)
// This is just for reference as the class was removed before public release
```
```typescript
import { useSession, getSession, updateSession, createRedisStore } from '@analog-tools/session';
const store = createRedisStore({ host: 'localhost', port: 6379 });
export default defineEventHandler(async (event) => {
await useSession(event, { store, secret: 'key' });
await updateSession(event, (data) => ({
visits: (data.visits || 0) + 1
})); // Auto-saves
});
```
1. Use `createRedisStore()` or `createMemoryStore()` for storage
2. Pass configuration directly to `useSession(event, config)`
3. Use `getSession(event)` to get current session data
4. Use `updateSession(event, updater)` to modify session data (auto-saves)
5. All operations are functional - no class instantiation needed
## Performance
- **Bundle Size**: ~4KB gzipped (significant reduction from previous versions)
- **Memory Usage**: Reduced through simplified architecture and direct storage integration
- **CPU**: Essential HMAC-SHA256 operations only, ~96 lines of crypto code
- **Tree Shaking**: Better dead code elimination with modern ESM build
## Security
- HMAC-SHA256 for cookie signing
- Timing attack resistant comparisons
- Secure cookie defaults
- Secret rotation support
- No over-engineered crypto that creates attack surfaces
## Contributing
Contributions are welcome! Please read our [Contributing Guide](../../CONTRIBUTING.md) for details.
## License
MIT © [Gregor Speck](https://github.com/MrBacony)