npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@periodic/tungsten-session

v1.0.0

Published

Production-grade session orchestration for Node.js with sliding expiry, idle timeout, and storage-agnostic adapters

Readme

🔑 Periodic Tungsten Session

npm version License: MIT TypeScript

Production-grade session orchestration for Node.js with sliding expiry, idle timeout, and storage-agnostic adapters

Part of the Periodic series of Node.js packages by Uday Thakur.


💡 Why Tungsten Session?

@periodic/tungsten-session is the session management layer of the @periodic/tungsten authentication family. While @periodic/tungsten handles cryptographic primitives — signing tokens, hashing passwords, verifying HMAC signatures — this library handles the server-side session lifecycle: creating sessions, validating them on every request, rotating IDs after privilege escalation, enforcing idle timeouts, and revoking sessions on logout or security events.

Sessions are deceptively simple until they aren't. A session that never expires is a permanent backdoor. A session that doesn't rotate on login is vulnerable to fixation attacks. A session store without support for bulk revocation leaves you powerless when a user's account is compromised. Tungsten Session handles all of it with a clean, storage-agnostic interface that works identically across Redis, PostgreSQL, DynamoDB, and any backend you choose.

The name represents:

  • Durability: Sessions persist correctly across requests, processes, and server restarts
  • Precision: Absolute expiry, sliding expiry, and idle timeout are independently configurable
  • Security: Rotation, revocation, and timing-safe lookups are built in — not bolted on
  • Flexibility: The storage interface is minimal — adapt it to any backend in minutes

Just as @periodic/tungsten handles the cryptographic layer without shortcuts, @periodic/tungsten-session handles the session layer without assumptions about your infrastructure.


🎯 Why Choose Tungsten Session?

Session management seems simple until you look closely — and most implementations miss at least one critical detail:

  • No session rotation leaves users vulnerable to session fixation attacks on login
  • No idle timeout means an inactive session stays valid until absolute expiry — a long window for hijacking
  • Absolute expiry only logs users out mid-task on long operations instead of after true inactivity
  • No bulk revocation means there's no way to log a user out of all devices after a password change
  • No storage abstraction means the session logic is tightly coupled to Redis or a specific ORM
  • Timing-unsafe lookups leak information about which session IDs exist via response time differences

Periodic Tungsten Session provides the perfect solution:

Storage-agnostic — Redis, PostgreSQL, DynamoDB, or any custom adapter
Session creation — cryptographically secure IDs, stored with full metadata
Session validation — checks absolute and idle expiry in a single call
Sliding expiration — idle timeout resets on each validated access
Absolute expiration — hard maximum lifetime, never extended
Idle timeout — separate inactivity window independent of absolute expiry
Session rotation — new ID on every sensitive operation, prevents fixation
Single revocation — invalidate one session by ID on explicit logout
Bulk revocation — invalidate all sessions for a user on security events
Timing-safe operations — no information leakage via response timing
Type-safe — strict TypeScript throughout, zero any
No global state — no side effects on import
Production-ready — non-blocking, never crashes your app


📦 Installation

npm install @periodic/tungsten-session @periodic/tungsten

Or with yarn:

yarn add @periodic/tungsten-session @periodic/tungsten

🚀 Quick Start

import { createSession, validateSession } from '@periodic/tungsten-session';

// 1. Define a storage adapter for your backend
const storage: SessionStorage = {
  async get(id: string) {
    const raw = await redis.get(`session:${id}`);
    return raw ? JSON.parse(raw) : null;
  },
  async set(session: Session) {
    await redis.set(`session:${session.id}`, JSON.stringify(session), 'EX', 604800);
  },
  async delete(id: string) {
    await redis.del(`session:${id}`);
  },
  async deleteByUserId(userId: string) {
    const keys = await redis.keys(`session:user:${userId}:*`);
    if (keys.length) await redis.del(...keys);
    return keys.length;
  },
};

// 2. Create a session on login
const { sessionId, session } = await createSession('user_123', {
  storage,
  ttl: '7d',
  idleTimeout: '24h',
});

// Set as a cookie
res.cookie('sid', sessionId, { httpOnly: true, secure: true, sameSite: 'strict' });

// 3. Validate on each request
const result = await validateSession(req.cookies.sid, {
  storage,
  extendOnAccess: true,
});

if (result.valid) {
  req.userId = result.session.userId;
}

Example session object:

{
  "id": "sess_01HQ4K2N8XVPQ3Z...",
  "userId": "user_123",
  "createdAt": 1708000000000,
  "lastAccessedAt": 1708003600000,
  "expiresAt": 1708604800000,
  "idleExpiresAt": 1708090000000
}

🧠 Core Concepts

The Session Lifecycle

Five functions cover the complete lifecycle — each does exactly one thing:

  • createSession — generates a secure session ID, builds the session record, stores it, returns the ID
  • validateSession — looks up the session, checks absolute and idle expiry, optionally extends idle TTL
  • rotateSession — atomically replaces the session ID — use after privilege escalation (login, sudo)
  • revokeSession — deletes a session by ID — use on explicit logout
  • revokeAllUserSessions — deletes all sessions for a user — use on password change or security event

Design principle:

The session functions are pure orchestration — they delegate all storage to your adapter. The same call works identically with Redis, Postgres, or an in-memory store.

Expiry Model

Three independent expiry mechanisms work together:

Session created
       │
       ├── Absolute expiry (ttl)        — hard maximum, never extended
       │
       └── Idle timeout (idleTimeout)   — reset on each validated access

Session valid while: now < expiresAt  AND  now < idleExpiresAt

A session expires when either limit is crossed — whichever comes first. The idle timeout resets on every call to validateSession with extendOnAccess: true. The absolute expiry never changes.

The SessionStorage Interface

The only contract between tungsten-session and your infrastructure — four methods, nothing more:

interface SessionStorage {
  get(id: string): Promise<Session | null>;
  set(session: Session): Promise<void>;
  delete(id: string): Promise<void>;
  deleteByUserId(userId: string): Promise<number>; // returns count deleted
}

✨ Features

🆕 Session Creation

const { sessionId, session } = await createSession('user_123', {
  storage,
  ttl: '7d',          // absolute expiry — '7d', '24h', '30m', or milliseconds
  idleTimeout: '24h', // idle expiry — reset on each access
  metadata: {         // optional — attach any extra data to the session
    ipAddress: req.ip,
    userAgent: req.headers['user-agent'],
  },
});

✅ Session Validation

Validate and optionally extend the idle timeout in a single call:

const result = await validateSession(sessionId, {
  storage,
  extendOnAccess: true,
});

if (result.valid) {
  console.log(result.session.userId);
} else {
  // result.reason: 'not_found' | 'expired' | 'idle_expired'
  console.log('Invalid:', result.reason);
}

🔄 Session Rotation

Replace the session ID atomically after sensitive operations — prevents fixation:

const { newSessionId } = await rotateSession(currentSessionId, { storage });
res.cookie('sid', newSessionId, { httpOnly: true, secure: true, sameSite: 'strict' });

🚪 Session Revocation

// Single session — explicit logout
await revokeSession(req.cookies.sid, storage);
res.clearCookie('sid');

// All sessions — password change, account compromise
const count = await revokeAllUserSessions('user_123', storage);
console.log(`Revoked ${count} sessions`);

📚 Common Patterns

1. Express Session Middleware

import { validateSession } from '@periodic/tungsten-session';

export async function sessionMiddleware(req, res, next) {
  const sessionId = req.cookies?.sid;
  if (!sessionId) return next();

  const result = await validateSession(sessionId, { storage, extendOnAccess: true });

  if (result.valid) {
    req.session = result.session;
    req.userId = result.session.userId;
  } else {
    res.clearCookie('sid');
  }

  next();
}

2. Login with Session Creation

import { verifyPassword } from '@periodic/tungsten';
import { createSession } from '@periodic/tungsten-session';

app.post('/auth/login', async (req, res) => {
  const { email, password } = req.body;
  const user = await db.users.findOne({ email });
  const isValid = await verifyPassword(password, user.passwordHash);
  if (!isValid) return res.status(401).json({ error: 'Invalid credentials' });

  const { sessionId } = await createSession(user.id, {
    storage,
    ttl: '7d',
    idleTimeout: '24h',
    metadata: { ipAddress: req.ip, userAgent: req.headers['user-agent'] },
  });

  res.cookie('sid', sessionId, { httpOnly: true, secure: true, sameSite: 'strict' });
  res.json({ userId: user.id });
});

3. Logout

import { revokeSession } from '@periodic/tungsten-session';

app.post('/auth/logout', async (req, res) => {
  if (req.cookies.sid) {
    await revokeSession(req.cookies.sid, storage);
    res.clearCookie('sid');
  }
  res.sendStatus(204);
});

4. Logout All Devices

import { revokeAllUserSessions } from '@periodic/tungsten-session';

app.post('/auth/logout-all', requireAuth, async (req, res) => {
  const count = await revokeAllUserSessions(req.userId, storage);
  res.clearCookie('sid');
  res.json({ revokedSessions: count });
});

5. Password Change with Full Revocation

import { hashPassword } from '@periodic/tungsten';
import { revokeAllUserSessions, createSession } from '@periodic/tungsten-session';

app.post('/auth/change-password', requireAuth, async (req, res) => {
  const hash = await hashPassword(req.body.newPassword);
  await db.users.update({ id: req.userId }, { passwordHash: hash });

  // Revoke all existing sessions — force re-login everywhere
  await revokeAllUserSessions(req.userId, storage);

  // Create a fresh session for the current device
  const { sessionId } = await createSession(req.userId, { storage, ttl: '7d' });
  res.cookie('sid', sessionId, { httpOnly: true, secure: true, sameSite: 'strict' });
  res.json({ message: 'Password changed — other sessions revoked' });
});

6. Privilege Escalation with Rotation

import { rotateSession } from '@periodic/tungsten-session';

app.post('/auth/sudo', requireAuth, async (req, res) => {
  const isValid = await verifyPassword(req.body.password, req.user.passwordHash);
  if (!isValid) return res.status(401).json({ error: 'Invalid password' });

  // Rotate session ID to prevent fixation after privilege change
  const { newSessionId } = await rotateSession(req.cookies.sid, { storage });
  res.cookie('sid', newSessionId, { httpOnly: true, secure: true, sameSite: 'strict' });
  res.json({ elevated: true });
});

7. Structured Logging Integration

import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';
import { validateSession } from '@periodic/tungsten-session';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

async function validate(sessionId: string) {
  const result = await validateSession(sessionId, { storage, extendOnAccess: true });
  if (!result.valid) {
    logger.warn('session.invalid', { reason: result.reason });
  } else {
    logger.info('session.valid', { userId: result.session.userId });
  }
  return result;
}

8. Production Configuration

import { createSession, validateSession, revokeAllUserSessions } from '@periodic/tungsten-session';

const isDevelopment = process.env.NODE_ENV === 'development';

export const sessionConfig = {
  storage,
  ttl: isDevelopment ? '1h' : '7d',
  idleTimeout: isDevelopment ? '15m' : '24h',
};

export const session = {
  create: (userId: string) => createSession(userId, sessionConfig),
  validate: (id: string) => validateSession(id, { ...sessionConfig, extendOnAccess: true }),
  revokeAll: (userId: string) => revokeAllUserSessions(userId, storage),
};

export default session;

🎛️ Configuration Options

createSession Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | storage | SessionStorage | required | Storage adapter | | ttl | string \| number | required | Absolute expiry ('7d', '24h', '30m', or ms) | | idleTimeout | string \| number | — | Idle timeout — reset on each valid access | | metadata | Record<string, unknown> | — | Optional data stored with the session |

validateSession Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | storage | SessionStorage | required | Storage adapter | | extendOnAccess | boolean | false | Reset idle timeout on each valid access |

rotateSession Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | storage | SessionStorage | required | Storage adapter | | ttl | string \| number | — | New absolute TTL for the rotated session |

SessionValidationResult

| Field | Type | Description | |-------|------|-------------| | valid | boolean | Whether the session is valid | | session | Session \| null | The session object (when valid: true) | | reason | 'not_found' \| 'expired' \| 'idle_expired' | Why invalid (when valid: false) |


📋 API Reference

Session Functions

createSession(userId: string, options: CreateSessionOptions): Promise<{ sessionId: string; session: Session }>
validateSession(sessionId: string, options: ValidateSessionOptions): Promise<SessionValidationResult>
rotateSession(sessionId: string, options: RotateSessionOptions): Promise<{ newSessionId: string }>
revokeSession(sessionId: string, storage: SessionStorage): Promise<void>
revokeAllUserSessions(userId: string, storage: SessionStorage): Promise<number>

Types

import type {
  Session,
  SessionStorage,
  CreateSessionOptions,
  ValidateSessionOptions,
  SessionValidationResult,
} from '@periodic/tungsten-session';

🧩 Architecture

@periodic/tungsten-session/
├── src/
│   ├── core/
│   │   ├── session.ts         # createSession, validateSession, rotateSession, revokeSession, revokeAllUserSessions
│   │   └── types.ts           # Session, SessionStorage, all options and result types
│   ├── utils/
│   │   ├── id.ts              # Cryptographically secure session ID generation
│   │   ├── ttl.ts             # TTL string parsing ('7d', '24h', '30m') → ms
│   │   └── time.ts            # Clock abstraction for deterministic testing
│   └── index.ts               # Public API

Design Philosophy:

  • Functions, not classes — each operation is an explicit function call, not a method on a stateful object
  • Storage is injected — no global adapter, no singleton, safe for multi-tenant apps
  • No framework coupling — works with Express, Fastify, Koa, or no framework at all
  • No database opinions — the storage interface is four methods, nothing more
  • Clock is injectable — pass a custom clock function for deterministic testing

📈 Performance

  • Single storage read per validation — no extra round trips for expiry checks
  • Atomic rotationrotateSession creates and deletes in the correct order, no window of inconsistency
  • Timing-safe — session ID lookups use constant-time comparison, no timing side channels
  • No global state — multiple configurations in the same process are fully isolated
  • No monkey-patching — pure functions only, no prototype mutation

🚫 Explicit Non-Goals

This package intentionally does not include:

❌ JWT or token signing (use @periodic/tungsten)
❌ Browser-side session state (use @periodic/tungsten-client)
❌ Built-in Redis, Postgres, or DynamoDB adapters — bring your own
❌ Cookie management — set cookies yourself with your framework
❌ Rate limiting (use @periodic/titanium)
❌ Magic or implicit behavior on import
❌ Configuration files (configure in code)

Focus on doing one thing well: correct, storage-agnostic server-side session orchestration.


🎨 TypeScript Support

Full TypeScript support with complete type safety:

import type {
  Session,
  SessionStorage,
  CreateSessionOptions,
  ValidateSessionOptions,
  SessionValidationResult,
} from '@periodic/tungsten-session';

// SessionValidationResult is discriminated — narrow by valid
const result = await validateSession(id, { storage });
if (result.valid) {
  result.session.userId; // string — only present when valid
} else {
  result.reason; // 'not_found' | 'expired' | 'idle_expired' — only when invalid
}

// Storage adapter is fully typed
const storage: SessionStorage = {
  get: async (id) => { /* return Session | null */ },
  set: async (session) => { /* store session */ },
  delete: async (id) => { /* delete by id */ },
  deleteByUserId: async (userId) => { /* return count deleted */ },
};

🧪 Testing

# Run tests
npm test

# Run tests with coverage
npm run test:coverage

# Run tests in watch mode
npm run test:watch

Note: All tests achieve >80% code coverage.


🤝 Related Packages

Part of the Periodic series by Uday Thakur:

Build complete, production-ready APIs with the Periodic series!


📖 Documentation


🛠️ Production Recommendations

Environment Variables

SESSION_TTL=7d
SESSION_IDLE_TIMEOUT=24h
NODE_ENV=production
REDIS_URL=redis://localhost:6379

Log Aggregation

Pair with @periodic/iridium for structured JSON output:

import { createLogger, ConsoleTransport, JsonFormatter } from '@periodic/iridium';

const logger = createLogger({
  transports: [new ConsoleTransport({ formatter: new JsonFormatter() })],
});

async function sessionMiddleware(req, res, next) {
  const result = await validateSession(req.cookies.sid, { storage, extendOnAccess: true });
  if (!result.valid) {
    logger.warn('session.invalid', { reason: result.reason, ip: req.ip });
  }
  next();
}

// Pipe to Elasticsearch, Datadog, CloudWatch, etc.

Security Monitoring

async function onPasswordChange(userId: string) {
  const count = await revokeAllUserSessions(userId, storage);
  Sentry.captureMessage('sessions.bulk_revocation', {
    level: 'info',
    extra: { userId, count },
  });
}

📝 License

MIT © Uday Thakur


🙏 Contributing

Contributions are welcome! Please read CONTRIBUTING.md for details on:

  • Code of conduct
  • Development setup
  • Pull request process
  • Coding standards
  • Architecture principles

📞 Support


🌟 Show Your Support

Give a ⭐️ if this project helped you build better applications!


Built with ❤️ by Uday Thakur for production-grade Node.js applications