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

v1.0.0

Published

Production-grade, security-auditable authentication primitives for Node.js with TypeScript support

Readme

🔩 Periodic Tungsten

npm version License: MIT TypeScript

Production-grade, security-auditable authentication primitives for Node.js with TypeScript support

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


💡 Why Tungsten?

Tungsten gets its name from the chemical element renowned for having the highest melting point of all metals — it holds its structure under conditions that destroy everything else. In engineering, tungsten is the material you reach for when the environment is too extreme for anything less. Just like tungsten performs where other materials fail, this library handles authentication under conditions where a mistake means a breach.

In chemistry, tungsten forms exceptionally strong carbide compounds used in cutting tools and armour-piercing projectiles — its strength comes not from softness or flexibility, but from density and resistance to deformation. Similarly, @periodic/tungsten is uncompromising: constant-time comparisons, memory-hard hashing, cryptographically secure generation, and no shortcuts.

The name represents:

  • Hardness: Cryptographic primitives that don't bend under attack
  • Precision: Every operation is explicit — no magic, no hidden configuration
  • Durability: Secure defaults that remain correct over time and key rotation
  • Purity: No framework coupling, no database assumptions, no transport opinions

Just as tungsten is the material engineers trust in the most demanding environments, @periodic/tungsten is the authentication layer you trust when the cost of failure is highest.


🎯 Why Choose Tungsten?

Authentication is one of the most common sources of critical security vulnerabilities — and most implementations get the subtle parts wrong:

  • DIY JWT libraries miss issuer and audience validation, opening the door to token confusion attacks
  • bcrypt is showing its age — Argon2id is the current OWASP recommendation and tungsten uses it by default
  • API key generation with Math.random() or weak entropy is endemic in backend codebases
  • No refresh token rotation means stolen tokens are valid forever
  • Timing-unsafe comparisons in API key and HMAC verification leak information to attackers
  • No key rotation support means rotating credentials requires a deployment instead of a config change

Periodic Tungsten provides the perfect solution:

Zero framework dependencies — works with Express, Fastify, Koa, or no framework at all
JWT Access & Refresh Tokens — HS256 and RS256 with key rotation built in
Argon2id Password Hashing — OWASP-recommended defaults, constant-time verification
API Key Generation — cryptographically secure with prefix support
Opaque Tokens — session identifier generation
TOTP (RFC 6238) — time-based one-time passwords for 2FA
HMAC Request Signing — webhook verification with replay protection
Cookie Utilities — secure configuration helpers
Multi-Tenant Key Abstraction — enterprise key management
Key Rotation — add, retire, and switch signing keys without downtime
Type-safe — strict TypeScript throughout, zero any
Tree-shakeable — ESM + CJS, import only what you use
No global state — no side effects on import
Production-ready — timing-safe, entropy-safe, no secret leakage


📦 Installation

npm install @periodic/tungsten

Or with yarn:

yarn add @periodic/tungsten

🚀 Quick Start

import {
  signAccessToken,
  verifyAccessToken,
  hashPassword,
  verifyPassword,
  generateApiKey,
  SimpleKeyProvider,
} from '@periodic/tungsten';

// JWT
const keyProvider = new SimpleKeyProvider('your-secret-key-min-32-chars', 'HS256');
const token = await signAccessToken({ sub: 'user_123', role: 'admin' }, {
  expiresIn: '15m',
  issuer: 'api.example.com',
  audience: 'dashboard',
  keyProvider,
});
const payload = await verifyAccessToken(token, { keyProvider, issuer: 'api.example.com', audience: 'dashboard' });

// Password
const hash = await hashPassword('MySecurePassword123!');
const isValid = await verifyPassword('MySecurePassword123!', hash);

// API Key
const apiKey = generateApiKey({ prefix: 'sk_live_' }); // sk_live_xYz123...

Example token payload:

{
  "sub": "user_123",
  "role": "admin",
  "iss": "api.example.com",
  "aud": "dashboard",
  "iat": 1708000000,
  "exp": 1708000900,
  "jti": "01HQ4K2N..."
}

🧠 Core Concepts

Key Providers

  • Key providers are the central abstraction — they decouple signing keys from the functions that use them
  • SimpleKeyProvider for single-tenant, single-key setups
  • RotatingKeyProvider for production systems that need to retire old keys without downtime
  • Pass the provider at call time — no global state, safe for multi-tenant apps
// Single key
const provider = new SimpleKeyProvider('your-secret-key', 'HS256');

// Key rotation — sign with new key, verify with old and new
const provider = new RotatingKeyProvider({
  kid: process.env.CURRENT_KEY_ID,
  secret: process.env.CURRENT_KEY_SECRET,
  algorithm: 'HS256',
});
provider.addKey(process.env.OLD_KEY_ID, process.env.OLD_KEY_SECRET, 'HS256');

Security Model

Design principle:

Every function is explicit. Nothing reads from process.env, nothing has global configuration, nothing silently falls back to insecure defaults. If a parameter is required for security, it is required by the type system.

  • All comparisons are timing-safe — no early exits that leak information
  • All generation uses crypto.randomBytes — no Math.random()
  • All hashing uses Argon2id with OWASP-recommended parameters
  • All JWT verification validates issuer and audience when provided

✨ Features

🔑 JWT Access & Refresh Tokens

Sign and verify tokens with HS256 or RS256, with key rotation and refresh token replay detection:

import { signAccessToken, verifyAccessToken, rotateRefreshToken } from '@periodic/tungsten';

// Sign
const token = await signAccessToken({ sub: 'user_123' }, {
  expiresIn: '15m',
  issuer: 'api.example.com',
  audience: 'dashboard',
  keyProvider,
});

// Verify
const payload = await verifyAccessToken(token, {
  keyProvider,
  issuer: 'api.example.com',
  audience: 'dashboard',
  clockTolerance: 60, // seconds
});

// Rotate refresh token with replay detection
const result = await rotateRefreshToken(oldToken, {
  keyProvider,
  onTokenReused: async (jti) => {
    logger.warn('Token reuse detected — revoking all sessions', { jti });
    await revokeAllUserSessions(jti);
  },
});

🔒 Password Hashing

Argon2id with OWASP-recommended defaults and constant-time verification:

import { hashPassword, verifyPassword } from '@periodic/tungsten';

const hash = await hashPassword('MySecurePassword123!');
// Defaults: 64MB memory, 3 iterations, parallelism 4

const isValid = await verifyPassword('MySecurePassword123!', hash);
// Constant-time — safe against timing attacks

🗝️ API Key Generation

Cryptographically secure generation with timing-safe verification:

import { generateApiKey, hashApiKey, verifyApiKey } from '@periodic/tungsten';

const apiKey = generateApiKey({ prefix: 'sk_live_', length: 32 });
// sk_live_xYz123... (crypto.randomBytes, min 16-byte entropy enforced)

const hash = hashApiKey(apiKey); // SHA-256, store this
const isValid = verifyApiKey(apiKey, hash); // timing-safe comparison

🔐 TOTP (Two-Factor Authentication)

RFC 6238 compliant, compatible with Google Authenticator and Authy:

import { generateTOTPSecret, generateTOTP, verifyTOTP } from '@periodic/tungsten';

const secret = generateTOTPSecret(); // show QR code to user
const code = generateTOTP(secret, { period: 30, digits: 6 });

const result = verifyTOTP(userProvidedCode, secret, { window: 1 });
if (result.valid) {
  console.log('2FA verified');
}

✍️ HMAC Request Signing

Webhook payload signing with replay protection:

import { signPayload, verifySignature } from '@periodic/tungsten';

const signature = signPayload({ event: 'user.created', userId: '123' }, 'webhook-secret');
const isValid = verifySignature(payload, signature, 'webhook-secret');
// Validates timestamp — rejects requests older than 5 minutes by default

🍪 Cookie Utilities

Secure cookie configuration helpers:

import { getSecureCookieOptions } from '@periodic/tungsten';

const options = getSecureCookieOptions({ 
  maxAge: 15 * 60, // 15 minutes
  sameSite: 'strict',
});
// httpOnly: true, secure: true, sameSite: 'strict' — safe defaults

🏢 Key Rotation

Add, retire, and switch signing keys without downtime:

import { RotatingKeyProvider } from '@periodic/tungsten';

const provider = new RotatingKeyProvider({
  kid: 'key-2025-01',
  secret: process.env.KEY_2025_01,
  algorithm: 'HS256',
});

// Add new key — start signing with it
provider.addKey('key-2025-02', process.env.KEY_2025_02, 'HS256');
provider.setCurrentKey('key-2025-02');

// Old key stays registered for verifying tokens still in circulation
// Remove it once all old tokens have expired

📚 Common Patterns

1. Authentication Middleware

import { verifyAccessToken, SimpleKeyProvider } from '@periodic/tungsten';

const keyProvider = new SimpleKeyProvider(process.env.JWT_SECRET, 'HS256');

export async function authMiddleware(req, res, next) {
  const token = req.headers.authorization?.replace('Bearer ', '');
  if (!token) return res.status(401).json({ error: 'Missing token' });

  try {
    const payload = await verifyAccessToken(token, {
      keyProvider,
      issuer: process.env.JWT_ISSUER,
      audience: process.env.JWT_AUDIENCE,
    });
    req.user = payload;
    next();
  } catch {
    res.status(401).json({ error: 'Invalid token' });
  }
}

2. Registration and Login

import { hashPassword, verifyPassword, signAccessToken } from '@periodic/tungsten';

async function register(email: string, password: string) {
  const hash = await hashPassword(password);
  await db.users.create({ email, passwordHash: hash });
}

async function login(email: string, password: string) {
  const user = await db.users.findOne({ email });
  const isValid = await verifyPassword(password, user.passwordHash);
  if (!isValid) throw new Error('Invalid credentials');

  return signAccessToken({ sub: user.id, role: user.role }, {
    expiresIn: '15m',
    keyProvider,
  });
}

3. Refresh Token Rotation

import { rotateRefreshToken } from '@periodic/tungsten';

app.post('/auth/refresh', async (req, res) => {
  try {
    const result = await rotateRefreshToken(req.cookies.refresh_token, {
      keyProvider,
      onTokenReused: async (jti) => {
        logger.warn('Refresh token reuse — possible theft', { jti });
        await db.sessions.revokeAll({ jti });
      },
    });

    res.cookie('refresh_token', result.newToken, getSecureCookieOptions({ maxAge: 7 * 24 * 60 * 60 }));
    res.json({ accessToken: result.accessToken });
  } catch {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

4. API Key Issuance and Verification

import { generateApiKey, hashApiKey, verifyApiKey } from '@periodic/tungsten';

// Issuance — show the plain key once, store only the hash
async function issueApiKey(userId: string) {
  const apiKey = generateApiKey({ prefix: 'sk_live_' });
  const hash = hashApiKey(apiKey);
  await db.apiKeys.create({ userId, hash, createdAt: new Date() });
  return apiKey; // return to user once — never stored
}

// Verification
async function verifyApiKeyRequest(providedKey: string) {
  const keys = await db.apiKeys.findAll();
  return keys.find(k => verifyApiKey(providedKey, k.hash));
}

5. TOTP Enrollment and Verification

import { generateTOTPSecret, verifyTOTP } from '@periodic/tungsten';
import QRCode from 'qrcode';

async function enrollTOTP(userId: string) {
  const secret = generateTOTPSecret();
  await db.users.update({ userId }, { totpSecret: secret, totpEnabled: false });

  const otpAuthUrl = `otpauth://totp/MyApp:${userId}?secret=${secret}&issuer=MyApp`;
  const qrCode = await QRCode.toDataURL(otpAuthUrl);
  return { secret, qrCode };
}

async function verifyAndActivateTOTP(userId: string, code: string) {
  const user = await db.users.findOne({ userId });
  const result = verifyTOTP(code, user.totpSecret);
  if (!result.valid) throw new Error('Invalid TOTP code');
  await db.users.update({ userId }, { totpEnabled: true });
}

6. Webhook Signing and Verification

import { signPayload, verifySignature } from '@periodic/tungsten';

// Signing outbound webhooks
async function sendWebhook(url: string, event: object) {
  const signature = signPayload(event, process.env.WEBHOOK_SECRET);
  await fetch(url, {
    method: 'POST',
    headers: { 'X-Signature': signature, 'Content-Type': 'application/json' },
    body: JSON.stringify(event),
  });
}

// Verifying inbound webhooks
app.post('/webhooks/stripe', (req, res) => {
  const isValid = verifySignature(req.body, req.headers['x-signature'], process.env.WEBHOOK_SECRET);
  if (!isValid) return res.status(401).json({ error: 'Invalid signature' });
  // process event
  res.sendStatus(200);
});

7. Structured Logging Integration

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

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

await rotateRefreshToken(oldToken, {
  keyProvider,
  onTokenReused: async (jti) => {
    logger.warn('tungsten.token_reuse', { jti, severity: 'high' });
    await revokeAllUserSessions(jti);
  },
});

8. Production Configuration

import {
  RotatingKeyProvider,
  signAccessToken,
  verifyAccessToken,
  hashPassword,
  verifyPassword,
} from '@periodic/tungsten';

const keyProvider = new RotatingKeyProvider({
  kid: process.env.JWT_KEY_ID,
  secret: process.env.JWT_KEY_SECRET,
  algorithm: 'HS256',
});

// Register previous key for tokens still in circulation
if (process.env.JWT_PREV_KEY_ID) {
  keyProvider.addKey(process.env.JWT_PREV_KEY_ID, process.env.JWT_PREV_KEY_SECRET, 'HS256');
}

export const auth = {
  sign: (payload: object) =>
    signAccessToken(payload, {
      expiresIn: '15m',
      issuer: process.env.JWT_ISSUER,
      audience: process.env.JWT_AUDIENCE,
      keyProvider,
    }),

  verify: (token: string) =>
    verifyAccessToken(token, {
      keyProvider,
      issuer: process.env.JWT_ISSUER,
      audience: process.env.JWT_AUDIENCE,
      clockTolerance: 60,
    }),

  hashPassword,
  verifyPassword,
};

export default auth;

🎛️ Configuration Options

signAccessToken Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | keyProvider | KeyProvider | required | Key provider instance | | expiresIn | string \| number | required | Expiration (e.g. '15m', '1h') | | issuer | string | — | Token issuer (iss claim) | | audience | string \| string[] | — | Token audience (aud claim) | | kid | string | — | Key ID override for rotation |

verifyAccessToken Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | keyProvider | KeyProvider | required | Key provider instance | | issuer | string | — | Expected issuer (validated if provided) | | audience | string \| string[] | — | Expected audience (validated if provided) | | clockTolerance | number | 60 | Clock skew tolerance in seconds |

generateApiKey Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | prefix | string | — | Key prefix (e.g. 'sk_live_') | | length | number | 32 | Key entropy in bytes (min: 16) |

generateTOTP / verifyTOTP Options

| Option | Type | Default | Description | |--------|------|---------|-------------| | period | number | 30 | Time step in seconds | | digits | number | 6 | Code length | | algorithm | 'SHA1' \| 'SHA256' \| 'SHA512' | 'SHA1' | Hash algorithm | | window | number | 1 | Verification tolerance window |

RotatingKeyProvider

| Method | Description | |--------|-------------| | addKey(kid, secret, algorithm) | Register an additional key for verification | | setCurrentKey(kid) | Switch signing to a different registered key |


📋 API Reference

JWT

signAccessToken(payload: JWTPayload, options: SignAccessTokenOptions): Promise<string>
verifyAccessToken(token: string, options: VerifyAccessTokenOptions): Promise<JWTPayload>
rotateRefreshToken(oldToken: string, options: RotateRefreshTokenOptions): Promise<RefreshTokenRotationResult>

Password

hashPassword(password: string): Promise<string>
verifyPassword(password: string, hash: string): Promise<boolean>

API Keys

generateApiKey(options?: GenerateApiKeyOptions): string
hashApiKey(apiKey: string): string
verifyApiKey(apiKey: string, hash: string): boolean

TOTP

generateTOTPSecret(): string
generateTOTP(secret: string, options?: TOTPOptions): string
verifyTOTP(code: string, secret: string, options?: TOTPOptions): TOTPVerificationResult

HMAC

signPayload(payload: unknown, secret: string): string
verifySignature(payload: unknown, signature: string, secret: string): boolean

Key Providers

new SimpleKeyProvider(secret: string, algorithm: Algorithm, kid?: string): KeyProvider
new RotatingKeyProvider(primaryKey: KeyConfig): KeyProvider

Types

import type {
  JWTPayload,
  KeyProvider,
  SignAccessTokenOptions,
  VerifyAccessTokenOptions,
  GenerateApiKeyOptions,
  TOTPOptions,
  TOTPVerificationResult,
  RefreshTokenRotationResult,
} from '@periodic/tungsten';

🧩 Architecture

@periodic/tungsten/
├── src/
│   ├── jwt/
│   │   ├── sign.ts           # signAccessToken()
│   │   ├── verify.ts         # verifyAccessToken()
│   │   └── refresh.ts        # rotateRefreshToken() + replay detection
│   ├── password/
│   │   └── index.ts          # hashPassword(), verifyPassword() — Argon2id
│   ├── apikey/
│   │   └── index.ts          # generateApiKey(), hashApiKey(), verifyApiKey()
│   ├── totp/
│   │   └── index.ts          # generateTOTPSecret(), generateTOTP(), verifyTOTP()
│   ├── hmac/
│   │   └── index.ts          # signPayload(), verifySignature()
│   ├── cookies/
│   │   └── index.ts          # getSecureCookieOptions()
│   ├── keys/
│   │   ├── simple.ts         # SimpleKeyProvider
│   │   └── rotating.ts       # RotatingKeyProvider
│   ├── types.ts               # All shared TypeScript interfaces
│   └── index.ts               # Public API

Design Philosophy:

  • Primitives only — no framework coupling, no database assumptions, no transport opinions
  • Explicit over implicit — every security parameter is required or has a safe default
  • No global state — all configuration is passed at call time
  • Timing-safe throughout — no early exits in comparisons that could leak information
  • Key providers decouple signing keys from the functions that use them — swap without changing call sites

📈 Performance

  • Argon2id is intentionally slow for password hashing — that's the security property
  • JWT signing and verification are async and non-blocking
  • API key generation uses crypto.randomBytes — synchronous but fast
  • TOTP verification is synchronous and sub-millisecond
  • No global state — multiple instances in the same process are fully isolated
  • Tree-shakeable — only the primitives you use end up in your bundle

🚫 Explicit Non-Goals

This package intentionally does not include:

❌ Session management or storage (bring your own database)
❌ OAuth / OpenID Connect flows (use a dedicated OAuth library)
❌ Framework middleware (adapt the primitives yourself)
❌ User model or database schema (no opinions on your data layer)
❌ Rate limiting (use @periodic/titanium for that)
❌ HTTP transport (no req/res coupling)
❌ Magic or implicit behavior on import
❌ Configuration files (configure in code)

Focus on doing one thing well: cryptographically sound, framework-agnostic authentication primitives.


🎨 TypeScript Support

Full TypeScript support with complete type safety:

import type {
  JWTPayload,
  KeyProvider,
  TOTPVerificationResult,
  RefreshTokenRotationResult,
} from '@periodic/tungsten';

// Generic payload — type flows through automatically
const payload = await verifyAccessToken<{ sub: string; role: 'admin' | 'user' }>(token, options);
payload.role; // typed as 'admin' | 'user'

// TOTPVerificationResult is discriminated
const result = verifyTOTP(code, secret);
if (result.valid) {
  result.delta; // number — present only when valid
}

🧪 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

Key Management

Store signing keys in environment variables or a secrets manager — never hardcode them:

JWT_KEY_ID=key-2025-01
JWT_KEY_SECRET=your-256-bit-secret-here
JWT_PREV_KEY_ID=key-2024-12        # keep until old tokens expire
JWT_PREV_KEY_SECRET=previous-secret
JWT_ISSUER=api.example.com
JWT_AUDIENCE=dashboard

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() })],
});

await rotateRefreshToken(oldToken, {
  keyProvider,
  onTokenReused: async (jti) => {
    logger.warn('tungsten.token_reuse', { jti, severity: 'critical' });
    await revokeAllUserSessions(jti);
  },
});

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

Security Monitoring

Capture authentication anomalies in your error tracker:

app.use(async (req, res, next) => {
  try {
    req.user = await verifyAccessToken(token, { keyProvider, issuer, audience });
    next();
  } catch (err) {
    Sentry.captureException(err, { extra: { url: req.url } });
    res.status(401).json({ error: 'Unauthorized' });
  }
});

📝 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