Highly ambitious ATProtocol AppView service and sdks

@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 session
  • getSession(sessionId) - Get session by ID
  • updateSession(sessionId, updates) - Update session data
  • deleteSession(sessionId) - Delete session
  • getSessionFromRequest(request) - Extract session from HTTP request
  • getCurrentUser(request) - Get user info from request
  • createSessionCookie(sessionId) - Create Set-Cookie header
  • createLogoutCookie() - Create logout cookie header
  • cleanup() - 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 refresh
  • logout(sessionId) - OAuth logout and session cleanup
  • hasValidOAuthTokens(sessionId) - Check token validity
  • getAccessToken(sessionId) - Get access token for API calls

How it works#

  1. OAuth callback returns tokens without sub
  2. createOAuthSession(tokens):
    • Creates temp OAuth client
    • Fetches user info to get sub
    • Creates session with userId
    • Stores tokens by sessionId (not userId!)
  3. 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