@slices/session#
Session management for Slice applications with OAuth integration.
Features#
- Multiple Storage Adapters: Memory, SQLite, PostgreSQL
- OAuth Integration: Works seamlessly with
@slices/oauth - Automatic Token Refresh: Keeps OAuth tokens valid
- Secure Cookie Handling: HttpOnly, Secure, SameSite defaults
- Automatic Cleanup: Expired session cleanup
- Framework Agnostic: Works with any Deno web framework
Installation#
deno add @slices/session
Quick Start#
Basic Usage#
import { SessionStore, SQLiteAdapter } from "@slices/session";
const sessionStore = new SessionStore({
adapter: new SQLiteAdapter("./sessions.db"),
cookieOptions: {
httpOnly: true,
secure: true,
sameSite: "lax"
}
});
// Create a session
const sessionId = await sessionStore.createSession("user123", "alice.bsky.social");
// Get session from request
const session = await sessionStore.getSessionFromRequest(request);
if (session) {
console.log("User:", session.handle);
}
With OAuth Integration#
import { SessionStore, SQLiteAdapter, withOAuthSession } from "@slices/session";
import { OAuthClient, SQLiteOAuthStorage } from "@slices/oauth";
const sessionStore = new SessionStore({
adapter: new SQLiteAdapter("./sessions.db")
});
const oauthStorage = new SQLiteOAuthStorage("./oauth.db");
const oauthConfig = {
clientId: "your-client-id",
clientSecret: "your-client-secret",
authBaseUrl: "https://auth.example.com",
redirectUri: "http://localhost:8000/oauth/callback",
scopes: ["atproto"],
};
const oauthSessions = withOAuthSession(
sessionStore,
oauthConfig,
oauthStorage,
{
autoRefresh: true
}
);
// OAuth callback flow
const tempClient = new OAuthClient(oauthConfig, oauthStorage, "temp");
const tokens = await tempClient.handleCallback({ code, state });
// Create OAuth session (handles user info fetch and token storage)
const sessionId = await oauthSessions.createOAuthSession(tokens);
// Get session with auto token refresh
const session = await oauthSessions.getOAuthSession(sessionId);
// Create session-scoped OAuth client
const sessionClient = new OAuthClient(oauthConfig, oauthStorage, sessionId);
const userInfo = await sessionClient.getUserInfo();
Storage Adapters#
Memory Adapter (Development)#
import { MemoryAdapter } from "@slices/session";
const adapter = new MemoryAdapter();
SQLite Adapter (Production Single Instance)#
import { SQLiteAdapter } from "@slices/session";
const adapter = new SQLiteAdapter("./sessions.db");
// or with URL
const adapter = new SQLiteAdapter("sqlite://./sessions.db");
PostgreSQL Adapter (Production Distributed)#
import { PostgresAdapter } from "@slices/session";
const adapter = new PostgresAdapter("postgresql://user:pass@localhost/db");
// Initialize the database table
await adapter.initialize();
API Reference#
SessionStore#
Main session management class.
const store = new SessionStore({
adapter: SessionAdapter,
cookieName?: string, // Default: "slice-session"
cookieOptions?: CookieOptions,
sessionTTL?: number, // Default: 30 days (ms)
cleanupInterval?: number, // Default: 1 hour (ms)
generateId?: () => string // Default: crypto.randomUUID()
});
Methods#
createSession(userId, handle?, data?)- Create new sessiongetSession(sessionId)- Get session by IDupdateSession(sessionId, updates)- Update session datadeleteSession(sessionId)- Delete sessiongetSessionFromRequest(request)- Extract session from HTTP requestgetCurrentUser(request)- Get user info from requestcreateSessionCookie(sessionId)- Create Set-Cookie headercreateLogoutCookie()- Create logout cookie headercleanup()- Remove expired sessions
OAuthSessionManager#
OAuth-enabled session management with session-scoped tokens.
const manager = withOAuthSession(
sessionStore,
oauthConfig,
oauthStorage,
{
autoRefresh: true, // Auto-refresh expired tokens
onTokenRefresh: async (sessionId, tokens) => {
// Handle token refresh
},
onLogout: async (sessionId) => {
// Handle logout
}
}
);
Methods#
createOAuthSession(tokens)- Create session with OAuth tokens (fetches user info, stores tokens by sessionId)getOAuthSession(sessionId)- Get session with token refreshlogout(sessionId)- OAuth logout and session cleanuphasValidOAuthTokens(sessionId)- Check token validitygetAccessToken(sessionId)- Get access token for API calls
How it works#
- OAuth callback returns tokens without
sub createOAuthSession(tokens):- Creates temp OAuth client
- Fetches user info to get
sub - Creates session with userId
- Stores tokens by sessionId (not userId!)
- All subsequent operations use sessionId for token lookup
Session Data Structure#
interface SessionData {
sessionId: string;
userId: string; // User's DID or ID
handle?: string; // User's handle (e.g., alice.bsky.social)
isAuthenticated: boolean;
data?: Record<string, unknown>; // Custom session data
createdAt: number; // Timestamp
expiresAt: number; // Timestamp
lastAccessedAt: number; // Timestamp
}
Framework Integration#
Deno Fresh#
// routes/_middleware.ts
import { SessionStore, SQLiteAdapter } from "@slices/session";
const sessionStore = new SessionStore({
adapter: new SQLiteAdapter("./sessions.db")
});
export async function handler(req: Request, ctx: FreshContext) {
const user = await sessionStore.getCurrentUser(req);
ctx.state.user = user;
return ctx.next();
}
Hono#
import { Hono } from "hono";
import { SessionStore, SQLiteAdapter } from "@slices/session";
const app = new Hono();
const sessionStore = new SessionStore({
adapter: new SQLiteAdapter("./sessions.db")
});
app.use("*", async (c, next) => {
const user = await sessionStore.getCurrentUser(c.req.raw);
c.set("user", user);
await next();
});
Multi-Device Support#
The session-scoped OAuth pattern enables proper multi-device support:
// User logs in on laptop
const laptopSessionId = await oauthSessions.createOAuthSession(laptopTokens);
const laptopClient = new OAuthClient(config, storage, laptopSessionId);
// Same user logs in on phone
const phoneSessionId = await oauthSessions.createOAuthSession(phoneTokens);
const phoneClient = new OAuthClient(config, storage, phoneSessionId);
// Each device has independent tokens
// Logging out laptop doesn't affect phone session
await oauthSessions.logout(laptopSessionId); // Only laptop session cleared
Security#
- Sessions are stored with secure, httpOnly cookies by default
- Automatic cleanup of expired sessions
- CSRF protection through SameSite cookies
- Secure session ID generation using crypto.randomUUID()
- Optional token refresh to keep OAuth sessions valid
- Session-scoped tokens prevent multi-device conflicts
License#
MIT