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

ecwt

v0.2.5

Published

Encrypted CBOR Web Token

Readme

ECWT

npm version License: MIT

ECWT is module for creating and verifying encrypted CBOR Web Tokens. It is designed to be used in situations where JWT is used, but there are major differences:

| | JWT | ECWT | | --- | --- | --- | | Encoding | 🧐 JSON with base64 | ✅ CBOR 2x smaller output | | Binary data | 🧐 Double base64 encoding | ✅ Supported out of the box | | Security | 📝 Signed Payload is readable by everyone | 🔒 Encrypted Payload is readable only by the private key possessor | | Metadata | ➕ Type and algorithm, increases size | ✅ No unnecessary metadata | | Revocation | 🧑‍💻 Requires additional implementation | ✅ Included with Redis |

Installation

ECWT depends on other modules, so you need to install them too.

npm install ecwt @kirick/snowflake
# or
pnpm install ecwt @kirick/snowflake
# or
bun install ecwt @kirick/snowflake

Some dependencies

EcwtFactory depends on other modules, so you might be need to install them too.

@kirick/snowflake to create unique IDs (required)

For documentation, see snowflake repository.

import { SnowflakeFactory } from '@kirick/snowflake';

const snowflakeFactory = new SnowflakeFactory({
  server_id: 0,
  worker_id: 0,
});

redis to store revoked tokens (optional)

import { createClient } from 'redis';

const redisClient = createClient({
  socket: {
    host: 'localhost',
    port: 6379,
  },
});

await redisClient.connect();

lru-cache to avoid decrypt the same token multiple times (optional)

import { LRUCache } from 'lru-cache';

const lruCache = new LRUCache({
  max: 1000, // maximum of 1000 items
  ttl: 60 * 60 * 1000, // 1 hour
});

Validation library of your choice (optional)

By specifying the schema, you also validate the payloads. Schema is a function that takes a value and returns it back or throws.

In our example, we use valibot library.

import * as v from 'valibot';

const validator = v.parser(
  v.object({
    user_id: v.pipe(
      v.number(),
      v.maxValue(10),
    ),
    nick: v.pipe(
      v.string(),
      v.maxLength(10),
    ),
  }),
);

That validator will prevent creating tokens for users with ID greater than 10 and nicknames longer than 10 characters.

Usage Examples

Initializing the EcwtFactory

First, configure the EcwtFactory with your environment dependencies:

import { EcwtFactory } from 'ecwt';
import { SnowflakeFactory } from '@kirick/snowflake';
import { LRUCache } from 'lru-cache';
import { createClient } from 'redis';

// Required: Initialize SnowflakeFactory for token ID generation
const snowflakeFactory = new SnowflakeFactory({
  server_id: 0,
  worker_id: 0,
});

// Optional but recommended: Configure LRU cache for performance optimization
const lruCache = new LRUCache({
  max: 1000, // Maximum cache size
  ttl: 60 * 60 * 1000, // Cache expiration (1 hour)
});

// Optional: Set up Redis client for token revocation capabilities
const redisClient = createClient({
  socket: {
    host: 'localhost',
    port: 6379,
  },
});
await redisClient.connect();

// Initialize the factory with your configuration
const ecwtFactory = new EcwtFactory({
  redisClient,
  lruCache,
  snowflakeFactory,
  options: {
    // Unique namespace for Redis keys to prevent collisions
    namespace: 'auth-service',
    // Your 64-byte encryption key (store securely)
    key: Buffer.from('YOUR_BASE64_KEY', 'base64'),
    // Schema validator for payload structure validation
    validator: myValidator,
  },
});

Token Generation

Generate tokens with precise payload and expiration controls:

// Create an access token with a 30-minute expiration
const ecwt = await ecwtFactory.create(
  {
    user_id: 123,
    name: "John Doe",
    role: "admin"
  },
  {
    ttl: 30 * 60 // 30 minutes in seconds
  }
);

// Get string representation of the token
const serializedToken = ecwt.token;

// Access token metadata
console.log(`Token ID: ${ecwt.id}`);
console.log(`Expiration timestamp: ${ecwt.ts_expired}`);
console.log(`Remaining validity: ${ecwt.getTTL()} seconds`);

Warning regarding non-expiring tokens:

When using ttl: null, revoked tokens remain in Redis storage indefinitely. This can lead to uncontrolled database growth over time as these tokens are never automatically purged. Consider implementing a periodic cleanup strategy if non-expiring tokens are required.

Token Verification

Implement verification with appropriate error handling:

import {
  EcwtExpiredError,
  EcwtRevokedError,
  EcwtParseError,
  EcwtInvalidError
} from 'ecwt';

try {
  // Verify and decode the token
  const verifiedToken = await ecwtFactory.verify(serializedToken);

  // Access verified payload data
  const { user_id, name, role } = verifiedToken.data;

  // Proceed with authenticated operation

} catch (error) {
  // Handle specific verification failures
  if (error instanceof EcwtExpiredError) {
    return respondWithError(401, "Authentication expired");
  } else if (error instanceof EcwtRevokedError) {
    return respondWithError(401, "Authentication revoked");
  } else if (error instanceof EcwtParseError) {
    return respondWithError(400, "Malformed authentication token");
  } else if (error instanceof EcwtInvalidError) {
    return respondWithError(401, "Invalid authentication token");
  } else {
    logger.error("Token verification error", error);
    return respondWithError(500, "Authentication service error");
  }
}

For exception-free verification, use safeVerify:

const { success, ecwt } = await ecwtFactory.safeVerify(serializedToken);

if (success) {
  // Proceed with authenticated request
  const userData = ecwt.data;
  return processAuthenticatedRequest(userData);
} else if (ecwt) {
  // Token structure was valid but failed verification
  logger.info(`Auth failure: token ${ecwt.id} is invalid`);
  return respondWithError(401, "Authentication token invalid");
} else {
  // Unparsable token structure
  logger.warn(`Auth failure: malformed token received`);
  return respondWithError(400, "Malformed authentication token");
}

Token Revocation

Implement secure session termination with token revocation:

// Terminate user session by revoking the token
await accessToken.revoke();
logger.info(`Session terminated: Token ${accessToken.id} revoked`);

// Subsequent verification attempts will fail with EcwtRevokedError
try {
  await ecwtFactory.verify(accessToken.token);
} catch (error) {
  if (error instanceof EcwtRevokedError) {
    // Expected behavior for revoked tokens
    logger.debug("Token verification correctly rejected revoked token");
  }
}

Advanced: Token Size Optimization

To reduce token size, use SenML key mapping that replaces string object keys with numeric identifiers throughout your entire payload structure. This compression works at any nesting depth. When implementing, catalog all potential keys across your schema and assign consistent numeric values to each, as these mappings cannot be changed once tokens are in circulation.

Important: The SenML key mapping configuration establishes a permanent relationship between field names and their numeric identifiers. Once deployed, these mappings must remain consistent to maintain compatibility with existing tokens. Adding new fields is acceptable, but changing existing mappings can break previously issued tokens.

// Standard configuration without key mapping
const standardFactory = new EcwtFactory({
  /* Core dependencies */
  options: {
    namespace: 'auth-service',
    key: encryptionKey,
  },
});

// Optimized configuration with key mapping
const optimizedFactory = new EcwtFactory({
  /* Core dependencies */
  options: {
    namespace: 'auth-service',
    key: encryptionKey,
    senml_key_map: {
      user_id: 1,
      name: 2,
      roles: 3,
      permissions: 4,
      metadata: 5,
      last_login: 6,
    },
  },
});

// Measure token size difference
const payload = {
  user_id: 12345,
  name: "John Smith",
  roles: ["admin", "editor"],
  permissions: ["read", "write", "delete"],
  metadata: { last_login: Date.now() },
};

const standardToken = await standardFactory.create(payload, { ttl: 3600 });
const optimizedToken = await optimizedFactory.create(payload, { ttl: 3600 });

console.log(`Standard token size: ${standardToken.token.length} bytes`);
console.log(`Optimized token size: ${optimizedToken.token.length} bytes`);
console.log(`Size reduction: ${(1 - optimizedToken.token.length / standardToken.token.length).toFixed(2) * 100}%`);

// Outputs:
// > Standard token size: 210 bytes
// > Optimized token size: 146 bytes
// > Size reduction: 30%