
Security News
Node.js Drops Bug Bounty Rewards After Funding Dries Up
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.
crypto-token-kit
Advanced tools
A modular, encrypted, DB-agnostic token system with optional refresh token support. Built as a secure alternative to JWT with built-in session management, revocation, and rotation capabilities.
A minimal, modular, and encrypted session token system designed as a secure alternative to JWT.
Supports optional refresh tokens with revocation and pluggable session storage.
Includes optional IP and fingerprint binding for enhanced security.
npm install crypto-token-kit dotenv
If using Redis store:
npm install ioredis
Create a .env file in your project root:
STK_ENC_KEY=your_32_byte_hex_key_here
STK_HMAC_KEY=your_64_byte_hex_key_here
Run this command to generate cryptographically strong keys:
node -e "console.log('STK_ENC_KEY=' + require('crypto').randomBytes(32).toString('hex')); console.log('STK_HMAC_KEY=' + require('crypto').randomBytes(64).toString('hex'))"
Copy the output and paste it into your .env file.
const {
createToken,
verifyToken,
refreshToken,
registerStore,
} = require("crypto-token-kit");
const memoryStore = require("crypto-token-kit/stores/memoryStore");
registerStore(memoryStore);
async function basicExample() {
// Create a simple access token
const { accessToken } = await createToken(
{ userId: "user-123", role: "admin" },
{ expiresIn: 900 } // 15 minutes
);
console.log("Access Token:", accessToken);
// Verify the token
const payload = await verifyToken(accessToken);
console.log("Verified Payload:", payload);
}
basicExample().catch(console.error);
async function advancedExample() {
// Create token with refresh support and security binding
const { accessToken, refreshToken: refresh } = await createToken(
{ userId: "user-123", email: "user@example.com" },
{
refresh: true,
expiresIn: 900, // 15 minutes for access token
refreshExpiresIn: 2592000, // 30 days for refresh token
bindIp: true,
bindFingerprint: true,
ip: "203.0.113.42",
fingerprint: "browser-fingerprint-string",
}
);
console.log("Access Token:", accessToken);
console.log("Refresh Token:", refresh);
// Verify token with security checks
try {
const payload = await verifyToken(accessToken, {
checkIp: true,
ip: "203.0.113.42",
checkFingerprint: true,
fingerprint: "browser-fingerprint-string",
});
console.log("Verified Payload:", payload);
} catch (error) {
console.error("Token verification failed:", error.message);
}
// Refresh tokens when access token expires
try {
const newTokens = await refreshToken(refresh, {
checkIp: true,
ip: "203.0.113.42",
checkFingerprint: true,
fingerprint: "browser-fingerprint-string",
});
console.log("New Access Token:", newTokens.accessToken);
console.log("New Refresh Token:", newTokens.refreshToken);
} catch (error) {
console.error("Token refresh failed:", error.message);
}
}
advancedExample().catch(console.error);
const express = require("express");
const { verifyToken } = require("crypto-token-kit");
const app = express();
// Authentication middleware
const authenticate = async (req, res, next) => {
const token = req.headers.authorization?.replace("Bearer ", "");
if (!token) {
return res.status(401).json({ error: "No token provided" });
}
try {
const payload = await verifyToken(token, {
checkIp: true,
ip: req.ip,
checkFingerprint: true,
fingerprint: req.headers["x-fingerprint"],
});
req.user = payload;
next();
} catch (error) {
res.status(401).json({ error: "Invalid token" });
}
};
// Protected route
app.get("/profile", authenticate, (req, res) => {
res.json({ user: req.user });
});
app.listen(3000, () => {
console.log("Server running on port 3000");
});
const memoryStore = require("crypto-token-kit/stores/memoryStore");
registerStore(memoryStore);
const redisStore = require("crypto-token-kit/stores/redisStore");
registerStore(redisStore);
Your custom store should implement these async methods:
class CustomStore {
async saveRefresh(refreshId, data) {
// Save refresh token data
// data: { userId, expiresAt, ip?, fingerprint? }
}
async isRefreshRevoked(refreshId) {
// Return true if refresh token is revoked
return false;
}
async revokeRefresh(refreshId) {
// Mark refresh token as revoked
}
// Optional: Session management
async saveSession(sessionId, data) {
// Save session data
}
async isSessionRevoked(sessionId) {
// Return true if session is revoked
return false;
}
async revokeSession(sessionId) {
// Mark session as revoked
}
}
const customStore = new CustomStore();
registerStore(customStore);
createToken(payload, options)Creates a new access token and optionally a refresh token.
Parameters:
payload (Object): Data to include in the tokenoptions (Object): Configuration optionsOptions:
expiresIn (Number): Access token expiration in seconds (default: 900)refresh (Boolean): Generate refresh token (default: false)refreshExpiresIn (Number): Refresh token expiration in seconds (default: 2592000)bindIp (Boolean): Bind token to IP address (default: false)bindFingerprint (Boolean): Bind token to fingerprint (default: false)ip (String): IP address to bind to (required if bindIp is true)fingerprint (String): Fingerprint to bind to (required if bindFingerprint is true)Returns: Promise<{ accessToken, refreshToken? }>
verifyToken(token, options)Verifies and decodes a token.
Parameters:
token (String): The token to verifyoptions (Object): Verification optionsOptions:
checkIp (Boolean): Verify IP binding (default: false)checkFingerprint (Boolean): Verify fingerprint binding (default: false)ip (String): Current IP address (required if checkIp is true)fingerprint (String): Current fingerprint (required if checkFingerprint is true)Returns: Promise<Object> - Decoded payload
refreshToken(refreshToken, options)Refreshes an access token using a refresh token.
Parameters:
refreshToken (String): The refresh tokenoptions (Object): Same options as verifyTokenReturns: Promise<{ accessToken, refreshToken }>
revokeToken(refreshToken)Revokes a refresh token.
Parameters:
refreshToken (String): The refresh token to revokeReturns: Promise<void>
Tokens can be bound to specific IP addresses to prevent token theft:
const { accessToken } = await createToken(payload, {
bindIp: true,
ip: "203.0.113.42",
});
// Verification will fail if IP doesn't match
await verifyToken(accessToken, {
checkIp: true,
ip: "203.0.113.42", // Must match bound IP
});
Tokens can be bound to browser/device fingerprints:
const { accessToken } = await createToken(payload, {
bindFingerprint: true,
fingerprint: "unique-browser-fingerprint",
});
Refresh tokens are automatically rotated on each use, invalidating the old token:
const newTokens = await refreshToken(oldRefreshToken);
// oldRefreshToken is now invalid
Run the built-in test suite:
node tests/basic.test.js
const assert = require("assert");
const { createToken, verifyToken } = require("crypto-token-kit");
async function runTests() {
// Test token creation and verification
const { accessToken } = await createToken({ userId: "test" });
const payload = await verifyToken(accessToken);
assert.strictEqual(payload.userId, "test");
console.log("✓ Basic token test passed");
// Test IP binding
const { accessToken: boundToken } = await createToken(
{ userId: "test" },
{ bindIp: true, ip: "127.0.0.1" }
);
try {
await verifyToken(boundToken, { checkIp: true, ip: "192.168.1.1" });
assert.fail("Should have failed IP check");
} catch (error) {
console.log("✓ IP binding test passed");
}
}
runTests().catch(console.error);
The library throws descriptive errors for different failure scenarios:
try {
await verifyToken(invalidToken);
} catch (error) {
switch (error.code) {
case "TOKEN_EXPIRED":
console.log("Token has expired");
break;
case "TOKEN_INVALID":
console.log("Token is malformed or tampered");
break;
case "IP_MISMATCH":
console.log("Token IP binding failed");
break;
case "FINGERPRINT_MISMATCH":
console.log("Token fingerprint binding failed");
break;
case "TOKEN_REVOKED":
console.log("Token has been revoked");
break;
default:
console.log("Unknown error:", error.message);
}
}
If you're migrating from JWT, here's a comparison:
| Feature | JWT | crypto-token-kit |
|---|---|---|
| Encryption | None (signed only) | AES-256-GCM |
| Token Size | Large | Compact |
| Revocation | Complex/Impossible | Built-in |
| Refresh Tokens | Manual implementation | Built-in |
| IP/Fingerprint Binding | Manual | Built-in |
| Session Storage | Stateless only | Pluggable |
Before (JWT):
const jwt = require("jsonwebtoken");
const token = jwt.sign(payload, secret, { expiresIn: "15m" });
const decoded = jwt.verify(token, secret);
After (crypto-token-kit):
const { createToken, verifyToken } = require("crypto-token-kit");
const { accessToken } = await createToken(payload, { expiresIn: 900 });
const decoded = await verifyToken(accessToken);
MIT License - see LICENSE file for details.
Made with ❤️ for secure applications
FAQs
A modular, encrypted, DB-agnostic token system with optional refresh token support. Built as a secure alternative to JWT with built-in session management, revocation, and rotation capabilities.
We found that crypto-token-kit demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?

Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.

Security News
Node.js has paused its bug bounty program after funding ended, removing payouts for vulnerability reports but keeping its security process unchanged.

Security News
The Axios compromise shows how time-dependent dependency resolution makes exposure harder to detect and contain.

Research
A supply chain attack on Axios introduced a malicious dependency, plain-crypto-js@4.2.1, published minutes earlier and absent from the project’s GitHub releases.