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

@db4/auth

v0.1.2

Published

JWT validation and authentication for db4 document database

Readme

@db4/auth

(GitHub, npm)

Your auth is a liability. Third-party services add 200ms latency. DIY implementations leak credentials. Monolithic libraries bloat your Workers. And while you struggle to coordinate rate limiting across distributed nodes, attackers slip through the gaps.

@db4/auth ends this. Edge-native JWT validation, session management, and rate limiting—zero dependencies, pure Web Crypto, built for Cloudflare Workers.

Features

  • JWT Validation - RS256, ES256, HS256 with JWKS auto-caching
  • Session Management - Create, validate, refresh, revoke with pluggable storage
  • Rate Limiting - Token bucket, sliding window, fixed window with composite IP/user limits
  • Edge-Native - Zero dependencies, Web Crypto API, Cloudflare Workers optimized

Installation

npm install @db4/auth

3 Steps to Secure Your Edge

1. Validate JWTs

import { validateJWT, JWKSClient, JWTExpiredError } from '@db4/auth'

const jwks = new JWKSClient({ cacheTTL: 600000 }) // 10-minute cache

export default {
  async fetch(request: Request): Promise<Response> {
    const authHeader = request.headers.get('Authorization')
    if (!authHeader?.startsWith('Bearer ')) {
      return new Response('Unauthorized', { status: 401 })
    }

    try {
      const keys = await jwks.getJWKS('https://auth.example.com/.well-known/jwks.json')
      const { payload } = await validateJWT(authHeader.slice(7), keys, {
        issuer: 'https://auth.example.com',
        audience: 'my-api',
        algorithms: ['RS256', 'ES256'],
      })

      return new Response(`Hello, ${payload.sub}!`)
    } catch (error) {
      if (error instanceof JWTExpiredError) {
        return new Response('Token expired', { status: 401 })
      }
      return new Response('Invalid token', { status: 401 })
    }
  },
}

2. Manage Sessions

import { SessionManager, SessionExpiredError, SessionRevokedError } from '@db4/auth'

const sessions = new SessionManager({ defaultExpiresIn: 86400 }) // 24 hours

// Create after login
const session = await sessions.createSession('user-123', {
  metadata: { device: 'mobile', ip: request.headers.get('CF-Connecting-IP') },
})

// Validate on requests
try {
  const validated = await sessions.validateSession(sessionId)
} catch (error) {
  if (error instanceof SessionExpiredError) {
    return new Response('Session expired', { status: 401 })
  }
  if (error instanceof SessionRevokedError) {
    return new Response('Session revoked', { status: 401 })
  }
}

// Revoke all on password change
await sessions.revokeAllUserSessions('user-123')

3. Rate Limit

import {
  CompositeRateLimiter,
  createRateLimitResponse,
  createCompositeRateLimitHeaders,
  applyRateLimitHeaders,
} from '@db4/auth'

const rateLimiter = new CompositeRateLimiter()

export default {
  async fetch(request: Request): Promise<Response> {
    const result = await rateLimiter.check(
      {
        ip: request.headers.get('CF-Connecting-IP') ?? undefined,
        userId: getUserIdFromToken(request),
      },
      {
        perIP: { limit: 100, window: 60 },    // 100/min per IP
        perUser: { limit: 1000, window: 60 }, // 1000/min per user
        global: { limit: 10000, window: 60 }, // 10k/min global
      },
    )

    if (!result.allowed) {
      return createRateLimitResponse(result.ip ?? result.user ?? result.global!)
    }

    const response = await handleRequest(request)
    return applyRateLimitHeaders(response, createCompositeRateLimitHeaders(result))
  },
}

API Reference

JWT Validation

validateJWT(token, key, options?)

Validates and decodes a JWT.

const result = await validateJWT(token, key, {
  issuer: 'https://auth.example.com',
  audience: 'my-app',
  algorithms: ['RS256', 'ES256'],
  clockTolerance: 30, // seconds
})

// result.payload - decoded claims
// result.header - JWT header (alg, typ, kid)

decodeJWT(token)

Decodes without verification (inspection only).

const { header, payload } = decodeJWT(token)

JWKSClient

Fetches and caches JWKS from identity providers.

const client = new JWKSClient({ cacheTTL: 600000 })

const jwks = await client.getJWKS('https://auth.example.com/.well-known/jwks.json')
const key = await client.getKey('https://auth.example.com/.well-known/jwks.json', 'key-id')
await client.refreshJWKS('https://auth.example.com/.well-known/jwks.json') // force refresh
client.clearCache()

Signature Verification

await verifyRS256(data, signature, rsaPublicKey)
await verifyES256(data, signature, ecPublicKey)
await verifyHS256(data, signature, secret)

Claims Validation

import { validateClaims, extractClaims } from '@db4/auth'

// Throws JWTExpiredError or JWTClaimsError on failure
validateClaims(payload, {
  issuer: 'https://auth.example.com',
  audience: 'my-app',
  clockTolerance: 30,
})

// Extract custom claims
const { role, permissions } = extractClaims<{ role: string; permissions: string[] }>(
  payload,
  ['role', 'permissions'],
)

Session Management

SessionManager

const manager = new SessionManager({
  storage: new InMemorySessionStorage(), // or custom
  defaultExpiresIn: 86400,
})

// Create
const session = await manager.createSession('user-id', {
  expiresIn: 3600,
  metadata: { device: 'mobile' },
})

// Validate (updates lastActivityAt)
const validated = await manager.validateSession(sessionId)
const validated = await manager.validateSession(sessionId, { updateActivity: false })

// Get without validation
const session = await manager.getSession(sessionId)

// Refresh
const refreshed = await manager.refreshSession(sessionId)
const refreshed = await manager.refreshSession(sessionId, { expiresIn: 7200 })

// Revoke (soft delete)
await manager.revokeSession(sessionId)

// Delete (hard delete)
await manager.deleteSession(sessionId)

// Bulk operations
await manager.revokeAllUserSessions('user-id')
await manager.deleteAllUserSessions('user-id')
await manager.cleanupExpiredSessions('user-id')

// Query
const all = await manager.getUserSessions('user-id')
const active = await manager.getActiveUserSessions('user-id')

Session Interface

interface Session {
  id: string
  userId: string
  createdAt: number      // Unix timestamp (seconds)
  expiresAt: number
  lastActivityAt: number
  revoked: boolean
  metadata?: Record<string, unknown>
}

Rate Limiting

TokenBucketRateLimiter

Best for allowing bursts while maintaining average rate.

const limiter = new TokenBucketRateLimiter()

const result = await limiter.consume('api-key', {
  capacity: 100,  // max burst
  refillRate: 10, // tokens/second
})

await limiter.peek('api-key', config)      // check without consuming
await limiter.reset('api-key')             // reset bucket
await limiter.consumeOrThrow('api-key', config) // throws on limit

SlidingWindowRateLimiter

Precise limiting with no boundary issues.

const limiter = new SlidingWindowRateLimiter()

const result = await limiter.consume('client-id', {
  limit: 100,
  window: 60,
}, 1) // optional weight

await limiter.peek('client-id', config)
await limiter.reset('client-id')
await limiter.consumeOrThrow('client-id', config)

FixedWindowRateLimiter

Simple and memory-efficient.

const limiter = new FixedWindowRateLimiter()

const result = await limiter.consume('client-id', { limit: 100, window: 60 })

await limiter.peek('client-id', config)
await limiter.reset('client-id')
await limiter.consumeOrThrow('client-id', config)

CompositeRateLimiter

Combine per-IP, per-user, and global limits.

const limiter = new CompositeRateLimiter()

const result = await limiter.check(
  { ip: '192.168.1.1', userId: 'user-123' },
  {
    perIP: { limit: 100, window: 60 },
    perUser: { limit: 1000, window: 60 },
    global: { limit: 10000, window: 60 },
  },
)

// result.allowed - false if ANY limit exceeded
// result.limitedBy - 'ip' | 'user' | 'global'
// result.retryAfter - max retry across all limits

await limiter.peek(context, config)
await limiter.reset({ ip: '192.168.1.1', userId: 'user-123' })
await limiter.resetGlobal()
await limiter.checkOrThrow(context, config)

Rate Limit Result

interface RateLimitResult {
  allowed: boolean
  remaining: number
  limit: number
  resetAt: number   // Unix timestamp
  retryAfter: number // seconds (0 if allowed)
}

Response Helpers

import {
  createRateLimitHeaders,
  createCompositeRateLimitHeaders,
  applyRateLimitHeaders,
  createRateLimitResponse,
} from '@db4/auth'

// Standard headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After
const headers = createRateLimitHeaders(result)
const headers = createCompositeRateLimitHeaders(compositeResult) // uses most restrictive

const response = applyRateLimitHeaders(new Response('OK'), headers)
const errorResponse = createRateLimitResponse(result)
const errorResponse = createRateLimitResponse(result, 'Custom message')

Custom Storage

Implement these interfaces for production use with KV, D1, or Durable Objects.

Session Storage

interface SessionStorage {
  get(sessionId: string): Promise<Session | null>
  set(session: Session): Promise<void>
  delete(sessionId: string): Promise<void>
  getByUserId(userId: string): Promise<Session[]>
  deleteByUserId(userId: string): Promise<void>
}

// Example: Cloudflare KV
class KVSessionStorage implements SessionStorage {
  constructor(private kv: KVNamespace) {}

  async get(sessionId: string) {
    return this.kv.get(`session:${sessionId}`, 'json')
  }

  async set(session: Session) {
    const ttl = session.expiresAt - Math.floor(Date.now() / 1000)
    await this.kv.put(`session:${session.id}`, JSON.stringify(session), {
      expirationTtl: Math.max(ttl, 60),
    })
    // Track by userId for getByUserId
    const userSessions = await this.kv.get(`user:${session.userId}`, 'json') as string[] ?? []
    if (!userSessions.includes(session.id)) {
      userSessions.push(session.id)
      await this.kv.put(`user:${session.userId}`, JSON.stringify(userSessions))
    }
  }
  // ... implement other methods
}

Rate Limit Storage

interface RateLimitStorage {
  get(key: string): Promise<{ count: number; windowStart: number } | null>
  set(key: string, count: number, windowStart: number, ttl: number): Promise<void>
  increment(key: string, windowStart: number, ttl: number): Promise<number>
  delete(key: string): Promise<void>
}

interface TokenBucketStorage {
  get(key: string): Promise<{ tokens: number; lastRefill: number } | null>
  set(key: string, state: { tokens: number; lastRefill: number }, ttl: number): Promise<void>
  delete(key: string): Promise<void>
}

interface SlidingWindowStorage {
  getEntries(key: string, windowStart: number): Promise<Array<{ timestamp: number; weight: number }>>
  addEntry(key: string, entry: { timestamp: number; weight: number }, ttl: number): Promise<void>
  delete(key: string): Promise<void>
}

Error Handling

All errors extend base classes for easy catching:

import {
  // JWT errors (extend JWTError)
  JWTError,
  JWTSignatureError,
  JWTExpiredError,
  JWTMalformedError,
  JWTUnsupportedAlgorithmError,
  JWTClaimsError,
  JWKSFetchError,
  JWKSKeyNotFoundError,

  // Session errors (extend SessionError)
  SessionError,
  SessionNotFoundError,
  SessionExpiredError,
  SessionRevokedError,

  // Rate limit errors
  RateLimitExceededError,
} from '@db4/auth'

try {
  await validateJWT(token, key)
} catch (error) {
  if (error instanceof JWTExpiredError) {
    // Token expired
  } else if (error instanceof JWTSignatureError) {
    // Invalid signature
  } else if (error instanceof JWTClaimsError) {
    // Issuer/audience/nbf validation failed
  } else if (error instanceof JWTMalformedError) {
    // Invalid token structure
  } else if (error instanceof JWKSKeyNotFoundError) {
    // Key ID not in JWKS
  } else if (error instanceof JWTError) {
    // Other JWT error
  }
}

try {
  await rateLimiter.checkOrThrow(context, config)
} catch (error) {
  if (error instanceof RateLimitExceededError) {
    return createRateLimitResponse(error.result)
  }
}

Without Edge-Native Auth

  • 200ms+ latency on every request to central auth
  • Security gaps between distributed workers
  • Single point of failure takes down everything
  • Failed audits from inconsistent session management
  • Abandoned users who won't wait for slow auth

With @db4/auth

  • Sub-millisecond validation with cached JWKS in-worker
  • Consistent rate limits across all edge locations
  • No external dependencies to fail
  • Audit-ready sessions with full lifecycle tracking
  • Invisible auth because instant auth is invisible

License

MIT