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 🙏

© 2025 – Pkg Stats / Ryan Hefner

@compyai/securetoken

v1.5.1

Published

A modular, encrypted, DB-agnostic token system with optional refresh support, honeypot protection, and revocation. A secure alternative to JWT.

Readme

Secure Token Toolkit

A robust Node.js library for creating and managing secure JWT-like tokens with built-in security features including honeypot protection, session management, and advanced binding options.

Features

  • AES-256-GCM Encryption: All token payloads are encrypted with industry-standard encryption
  • HMAC-SHA512 Signing: Cryptographic signatures prevent token tampering
  • Honeypot Protection: Built-in protection against brute force attacks with decoy tokens
  • Session Management: Optional session storage and revocation capabilities
  • IP & Fingerprint Binding: Bind tokens to specific IP addresses or browser fingerprints
  • Refresh Token Support: Secure token refresh mechanism
  • Timing Attack Protection: Uses timing-safe comparison for signature verification

Installation

npm install @compyai/securetoken

Quick Start

const { createSecureTokenToolkit } = require("@compyai/securetoken");

// Initialize the toolkit with encryption keys
const toolkit = createSecureTokenToolkit({
  encKey: "your-256-bit-encryption-key-in-hex", // 64 hex characters
  hmacKey: "your-256-bit-hmac-key-in-hex", // 64 hex characters
});

// Create a token
const { accessToken, refreshToken } = await toolkit.createToken(
  { userId: 123, role: "user" },
  { expiresIn: 900, refresh: true }
);

// Verify a token
try {
  const payload = await toolkit.verifyToken(accessToken);
  console.log("User ID:", payload.userId);
} catch (error) {
  console.error("Token verification failed:", error.message);
}

How It Works

graph TB
    A[Client Request] --> B{Token Valid?}
    B -->|No| C[Check Honeypot]
    C -->|Under Threshold| D[Return Error]
    C -->|Over Threshold| E[Return Decoy Token]
    E --> F[Log Attack Attempt]
    B -->|Yes| G[Decrypt Payload]
    G --> H{Check Bindings}
    H -->|IP/Fingerprint Mismatch| I[Return Error]
    H -->|Valid| J{Session Active?}
    J -->|Revoked| K[Return Error]
    J -->|Active| L[Return Payload]

    style A fill:#e1f5fe
    style L fill:#e8f5e8
    style D fill:#ffebee
    style E fill:#fff3e0
    style F fill:#fff3e0

Configuration

Basic Configuration

const config = {
  encKey: "your-256-bit-encryption-key-in-hex",
  hmacKey: "your-256-bit-hmac-key-in-hex",
};

Advanced Configuration with Honeypot

const config = {
  encKey: "your-256-bit-encryption-key-in-hex",
  hmacKey: "your-256-bit-hmac-key-in-hex",
  honeypot: {
    enabled: true,
    failThreshold: 5,
    decoyPayload: { userId: 999, role: "admin" },
    slowResponses: true,
    onTriggered: (failInfo) => {
      console.log("Honeypot triggered:", failInfo);
    },
    onDecoyUse: (payload) => {
      console.log("Decoy token used:", payload);
    },
  },
  store: {
    saveSession: async (sessionId, data) => {
      // Save session to your database
    },
    saveRefresh: async (sessionId, data) => {
      // Save refresh token info to your database
    },
    isSessionRevoked: async (sessionId) => {
      // Check if session is revoked
      return false;
    },
    isRefreshRevoked: async (sessionId) => {
      // Check if refresh token is revoked
      return false;
    },
    revokeRefresh: async (sessionId) => {
      // Revoke refresh token
    },
  },
};

Session Store Backends

The toolkit supports various session storage backends. Here are complete implementation examples:

Redis Store (Recommended for Production)

const Redis = require("ioredis");
const { createSecureTokenToolkit } = require("@compyai/securetoken");

const redis = new Redis({
  host: "localhost",
  port: 6379,
  retryDelayOnFailover: 100,
  maxRetriesPerRequest: 3,
});

const redisStore = {
  async saveSession(sessionId, data) {
    const key = `session:${sessionId}`;
    await redis.setex(
      key,
      data.exp - Math.floor(Date.now() / 1000),
      JSON.stringify(data)
    );
  },

  async saveRefresh(sessionId, data) {
    const key = `refresh:${sessionId}`;
    await redis.setex(
      key,
      data.exp - Math.floor(Date.now() / 1000),
      JSON.stringify(data)
    );
  },

  async isSessionRevoked(sessionId) {
    const key = `session:${sessionId}`;
    const result = await redis.get(key);
    return result === null;
  },

  async isRefreshRevoked(sessionId) {
    const key = `refresh:${sessionId}`;
    const result = await redis.get(key);
    return result === null;
  },

  async revokeRefresh(sessionId) {
    const key = `refresh:${sessionId}`;
    await redis.del(key);
  },

  async revokeSession(sessionId) {
    const sessionKey = `session:${sessionId}`;
    const refreshKey = `refresh:${sessionId}`;
    await redis.del([sessionKey, refreshKey]);
  },
};

const toolkit = createSecureTokenToolkit({
  encKey: process.env.TOKEN_ENC_KEY,
  hmacKey: process.env.TOKEN_HMAC_KEY,
  store: redisStore,
});

Database Store (PostgreSQL Example)

const { Pool } = require("pg");
const { createSecureTokenToolkit } = require("@compyai/securetoken");

const pool = new Pool({
  user: "username",
  host: "localhost",
  database: "myapp",
  password: "password",
  port: 5432,
});

// Database schema:
// CREATE TABLE sessions (
//   session_id UUID PRIMARY KEY,
//   data JSONB NOT NULL,
//   expires_at TIMESTAMP NOT NULL,
//   created_at TIMESTAMP DEFAULT NOW()
// );
//
// CREATE TABLE refresh_tokens (
//   session_id UUID PRIMARY KEY,
//   data JSONB NOT NULL,
//   expires_at TIMESTAMP NOT NULL,
//   created_at TIMESTAMP DEFAULT NOW()
// );

const dbStore = {
  async saveSession(sessionId, data) {
    const expiresAt = new Date(data.exp * 1000);
    await pool.query(
      "INSERT INTO sessions (session_id, data, expires_at) VALUES ($1, $2, $3) ON CONFLICT (session_id) DO UPDATE SET data = $2, expires_at = $3",
      [sessionId, JSON.stringify(data), expiresAt]
    );
  },

  async saveRefresh(sessionId, data) {
    const expiresAt = new Date(data.exp * 1000);
    await pool.query(
      "INSERT INTO refresh_tokens (session_id, data, expires_at) VALUES ($1, $2, $3) ON CONFLICT (session_id) DO UPDATE SET data = $2, expires_at = $3",
      [sessionId, JSON.stringify(data), expiresAt]
    );
  },

  async isSessionRevoked(sessionId) {
    const result = await pool.query(
      "SELECT 1 FROM sessions WHERE session_id = $1 AND expires_at > NOW()",
      [sessionId]
    );
    return result.rows.length === 0;
  },

  async isRefreshRevoked(sessionId) {
    const result = await pool.query(
      "SELECT 1 FROM refresh_tokens WHERE session_id = $1 AND expires_at > NOW()",
      [sessionId]
    );
    return result.rows.length === 0;
  },

  async revokeRefresh(sessionId) {
    await pool.query("DELETE FROM refresh_tokens WHERE session_id = $1", [
      sessionId,
    ]);
  },

  async revokeSession(sessionId) {
    const client = await pool.connect();
    try {
      await client.query("BEGIN");
      await client.query("DELETE FROM sessions WHERE session_id = $1", [
        sessionId,
      ]);
      await client.query("DELETE FROM refresh_tokens WHERE session_id = $1", [
        sessionId,
      ]);
      await client.query("COMMIT");
    } catch (error) {
      await client.query("ROLLBACK");
      throw error;
    } finally {
      client.release();
    }
  },
};

const toolkit = createSecureTokenToolkit({
  encKey: process.env.TOKEN_ENC_KEY,
  hmacKey: process.env.TOKEN_HMAC_KEY,
  store: dbStore,
});

In-Memory Store (Development/Testing)

const { createSecureTokenToolkit } = require("@compyai/securetoken");

class MemoryStore {
  constructor() {
    this.sessions = new Map();
    this.refreshTokens = new Map();
    this.cleanup();
  }

  async saveSession(sessionId, data) {
    this.sessions.set(sessionId, {
      data,
      expiresAt: data.exp * 1000,
    });
  }

  async saveRefresh(sessionId, data) {
    this.refreshTokens.set(sessionId, {
      data,
      expiresAt: data.exp * 1000,
    });
  }

  async isSessionRevoked(sessionId) {
    const session = this.sessions.get(sessionId);
    if (!session) return true;
    if (Date.now() > session.expiresAt) {
      this.sessions.delete(sessionId);
      return true;
    }
    return false;
  }

  async isRefreshRevoked(sessionId) {
    const refresh = this.refreshTokens.get(sessionId);
    if (!refresh) return true;
    if (Date.now() > refresh.expiresAt) {
      this.refreshTokens.delete(sessionId);
      return true;
    }
    return false;
  }

  async revokeRefresh(sessionId) {
    this.refreshTokens.delete(sessionId);
  }

  async revokeSession(sessionId) {
    this.sessions.delete(sessionId);
    this.refreshTokens.delete(sessionId);
  }

  // Cleanup expired entries every 5 minutes
  cleanup() {
    setInterval(() => {
      const now = Date.now();

      for (const [sessionId, session] of this.sessions.entries()) {
        if (now > session.expiresAt) {
          this.sessions.delete(sessionId);
        }
      }

      for (const [sessionId, refresh] of this.refreshTokens.entries()) {
        if (now > refresh.expiresAt) {
          this.refreshTokens.delete(sessionId);
        }
      }
    }, 5 * 60 * 1000);
  }
}

const memoryStore = new MemoryStore();

const toolkit = createSecureTokenToolkit({
  encKey: process.env.TOKEN_ENC_KEY,
  hmacKey: process.env.TOKEN_HMAC_KEY,
  store: memoryStore,
});

MongoDB Store

const { MongoClient } = require("mongodb");
const { createSecureTokenToolkit } = require("@compyai/securetoken");

const client = new MongoClient("mongodb://localhost:27017");
const db = client.db("myapp");

// Create indexes for automatic expiration
// db.sessions.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 });
// db.refreshTokens.createIndex({ "expiresAt": 1 }, { expireAfterSeconds: 0 });

const mongoStore = {
  async saveSession(sessionId, data) {
    await db.collection("sessions").replaceOne(
      { sessionId },
      {
        sessionId,
        data,
        expiresAt: new Date(data.exp * 1000),
        createdAt: new Date(),
      },
      { upsert: true }
    );
  },

  async saveRefresh(sessionId, data) {
    await db.collection("refreshTokens").replaceOne(
      { sessionId },
      {
        sessionId,
        data,
        expiresAt: new Date(data.exp * 1000),
        createdAt: new Date(),
      },
      { upsert: true }
    );
  },

  async isSessionRevoked(sessionId) {
    const session = await db.collection("sessions").findOne({
      sessionId,
      expiresAt: { $gt: new Date() },
    });
    return !session;
  },

  async isRefreshRevoked(sessionId) {
    const refresh = await db.collection("refreshTokens").findOne({
      sessionId,
      expiresAt: { $gt: new Date() },
    });
    return !refresh;
  },

  async revokeRefresh(sessionId) {
    await db.collection("refreshTokens").deleteOne({ sessionId });
  },

  async revokeSession(sessionId) {
    await Promise.all([
      db.collection("sessions").deleteOne({ sessionId }),
      db.collection("refreshTokens").deleteOne({ sessionId }),
    ]);
  },
};

const toolkit = createSecureTokenToolkit({
  encKey: process.env.TOKEN_ENC_KEY,
  hmacKey: process.env.TOKEN_HMAC_KEY,
  store: mongoStore,
});

API Reference

createToken(payload, options)

Creates a new encrypted and signed token.

Parameters:

  • payload (Object): The data to include in the token
  • options (Object): Token creation options
    • expiresIn (Number): Token expiration time in seconds (default: 900)
    • refresh (Boolean): Whether to create a refresh token (default: false)
    • refreshExpiresIn (Number): Refresh token expiration in seconds (default: 2592000)
    • bindIp (Boolean): Bind token to IP address
    • ip (String): IP address to bind to
    • bindFingerprint (Boolean): Bind token to browser fingerprint
    • fingerprint (String): Browser fingerprint to bind to

Returns: { accessToken, refreshToken }

const tokens = await toolkit.createToken(
  { userId: 123, email: "[email protected]" },
  {
    expiresIn: 3600,
    refresh: true,
    bindIp: true,
    ip: "192.168.1.1",
  }
);

verifyToken(token, options)

Verifies and decrypts a token.

Parameters:

  • token (String): The token to verify
  • options (Object): Verification options
    • checkIp (Boolean): Whether to check IP binding
    • ip (String): Current IP address
    • checkFingerprint (Boolean): Whether to check fingerprint binding
    • fingerprint (String): Current browser fingerprint
    • ua (String): User agent for honeypot tracking

Returns: Decrypted payload object

try {
  const payload = await toolkit.verifyToken(token, {
    checkIp: true,
    ip: req.ip,
    ua: req.get("User-Agent"),
  });
  console.log("Token valid:", payload);
} catch (error) {
  console.error("Invalid token:", error.message);
}

refreshToken(refreshToken, options)

Creates a new access token using a refresh token.

Parameters:

  • refreshToken (String): The refresh token
  • options (Object): Same options as verifyToken

Returns: New token pair { accessToken, refreshToken }

const newTokens = await toolkit.refreshToken(oldRefreshToken, {
  ip: req.ip,
  ua: req.get("User-Agent"),
});

Security Features

Honeypot Protection

The honeypot system protects against brute force attacks by:

  1. Tracking failed verification attempts by IP and User-Agent
  2. When the failure threshold is reached, returning decoy tokens instead of errors
  3. Logging when decoy tokens are used, indicating a potential attacker

Session Management

Optional session storage allows you to:

  • Track active sessions
  • Revoke sessions remotely
  • Implement logout functionality
  • Monitor token usage

Token Binding

Bind tokens to specific contexts:

  • IP Binding: Tokens only work from the original IP address
  • Fingerprint Binding: Tokens tied to browser fingerprints

Best Practices

  1. Key Generation: Use cryptographically secure random keys:

    const crypto = require("crypto");
    const encKey = crypto.randomBytes(32).toString("hex");
    const hmacKey = crypto.randomBytes(32).toString("hex");
  2. Environment Variables: Store keys securely:

    const config = {
      encKey: process.env.TOKEN_ENC_KEY,
      hmacKey: process.env.TOKEN_HMAC_KEY,
    };
  3. Short Expiration: Use short-lived access tokens (15 minutes or less)

  4. Secure Storage: Implement proper session storage with your database

  5. HTTPS Only: Always use HTTPS in production

  6. Rate Limiting: Implement additional rate limiting at the application level

Error Handling

The toolkit throws specific errors for different failure scenarios:

  • "Invalid token": Token is malformed or signature verification failed
  • "Expired": Token has passed its expiration time
  • "IP mismatch": Token IP binding doesn't match current IP
  • "Fingerprint mismatch": Token fingerprint binding doesn't match
  • "Revoked session": Session has been revoked
  • "Revoked refresh": Refresh token has been revoked
  • "Not a refresh token": Attempted to refresh with an access token

License

MIT

Links

  • Website: https://compyai.xyz

Contributing

Pull requests are welcome. For major changes, please open an issue first to discuss what you would like to change.

Security

If you discover a security vulnerability, please send an email to [email protected]. All security vulnerabilities will be promptly addressed.