simple-jwt-id
v1.0.0-beta.1
Published
A lightweight JWT library with multiple algorithm support and token revocation
Maintainers
Readme
simple-jwt-id
🔐 A lightweight JWT (JSON Web Token) library for Node.js with multiple algorithm support, token revocation, and zero configuration. Think of it as a simple yet powerful JWT handler with built-in blacklist support.
✨ Features
- Create and verify JWT tokens with ease
- Refresh token support with automatic rotation
- Support for multiple algorithms: HS256, HS512, RS256, RS512, PS256, ES256
- Built-in token revocation with automatic blacklist management
- Standard JWT claims: iat, exp, nbf, aud, iss, sub, jti
- Decode tokens without verification
- Custom error classes for better error handling
- TypeScript definitions included
- Works with both CommonJS and ES Modules
- Minimal dependencies (only simple-cache-id for blacklist)
📦 Installation
npm install simple-jwt-id🚀 Usage
Basic Usage (CommonJS)
const { createToken, verifyToken, decodeToken } = require("simple-jwt-id");
const SECRET = "your-secret-key";
(async () => {
// Create a token
const token = await createToken(
{ userId: 123, username: "john_doe" },
SECRET,
{ expiresIn: 3600 } // 1 hour
);
console.log("Token:", token);
// Verify token
const payload = await verifyToken(token, SECRET);
console.log("Payload:", payload);
// Decode without verification
const decoded = decodeToken(token);
console.log("Decoded:", decoded);
})();ES Modules
import { createToken, verifyToken, decodeToken } from "simple-jwt-id";
const SECRET = "your-secret-key";
// Create token with custom claims
const token = await createToken({ userId: 789, role: "admin" }, SECRET, {
algorithm: "HS512",
expiresIn: 7200, // 2 hours
issuer: "my-app",
audience: "my-api",
});
// Verify with validation
const payload = await verifyToken(token, SECRET, {
audience: "my-api",
issuer: "my-app",
});Token Revocation
const { createToken, verifyToken, revokeToken } = require("simple-jwt-id");
const SECRET = "your-secret-key";
(async () => {
// Create token
const token = await createToken({ userId: 456 }, SECRET);
// Verify - works fine
await verifyToken(token, SECRET);
// Revoke token
await revokeToken(token);
// Verify again - throws error
try {
await verifyToken(token, SECRET);
} catch (error) {
console.log(error.message); // "Token revoked"
}
})();Refresh Token (Access + Refresh Token Pair)
const {
createTokenPair,
refreshTokens,
verifyToken,
} = require("simple-jwt-id");
const SECRET = "your-secret-key";
(async () => {
// Step 1: Login - Create token pair
const { accessToken, refreshToken } = await createTokenPair(
{ userId: 123, role: "admin" },
SECRET,
{
accessExpiresIn: 900, // 15 minutes
refreshExpiresIn: 604800, // 7 days
}
);
console.log("Access Token:", accessToken);
console.log("Refresh Token:", refreshToken);
// Step 2: Use access token for API calls
const payload = await verifyToken(accessToken, SECRET, {
audience: "api",
});
console.log("Verified:", payload);
// Step 3: When access token expires, refresh it
const {
accessToken: newAccessToken,
refreshToken: newRefreshToken,
} = await refreshTokens(refreshToken, SECRET);
console.log("New Access Token:", newAccessToken);
console.log("New Refresh Token:", newRefreshToken);
// Step 4: Old refresh token is now revoked (rotation enabled by default)
try {
await verifyToken(refreshToken, SECRET, { audience: "refresh" });
} catch (error) {
console.log(error.message); // "Token revoked"
}
})();Refresh Token Without Rotation
const { createTokenPair, refreshTokens } = require("simple-jwt-id");
const SECRET = "your-secret-key";
(async () => {
// Create token pair
const { refreshToken } = await createTokenPair(
{ userId: 456 },
SECRET
);
// Refresh without rotation - old refresh token remains valid
const { accessToken } = await refreshTokens(refreshToken, SECRET, {
rotateRefreshToken: false,
});
console.log("New Access Token:", accessToken);
// Old refresh token can still be used
const result = await refreshTokens(refreshToken, SECRET, {
rotateRefreshToken: false,
});
console.log("Another Access Token:", result.accessToken);
})();Advanced Usage
const {
createToken,
verifyToken,
decodeToken,
TokenExpiredError,
NotBeforeError,
} = require("simple-jwt-id");
const SECRET = "your-secret-key";
(async () => {
// Token with notBefore claim
const token = await createToken({ action: "verify-email" }, SECRET, {
expiresIn: 600, // 10 minutes
notBefore: 60, // Valid after 1 minute
subject: "[email protected]",
});
// Try to use immediately - throws NotBeforeError
try {
await verifyToken(token, SECRET);
} catch (error) {
if (error instanceof NotBeforeError) {
console.log("Token not yet valid!");
}
}
// Decode with complete info
const { header, payload } = decodeToken(token, { complete: true });
console.log("Algorithm:", header.alg);
console.log("Subject:", payload.sub);
// Ignore expiration (useful for debugging)
const expiredToken = await createToken({ test: true }, SECRET, {
expiresIn: 1,
});
await new Promise((resolve) => setTimeout(resolve, 2000));
// This would throw TokenExpiredError
// await verifyToken(expiredToken, SECRET);
// But this works
const payload = await verifyToken(expiredToken, SECRET, {
ignoreExpiration: true,
});
console.log("Payload:", payload);
})();🧪 Testing
npm testJest is used for testing. All tests must pass before publishing.
Test Coverage:
- 68 unit tests covering all features
- Both CommonJS and ES Module tests
- Token creation, verification, decoding, and revocation
- Refresh token functionality with rotation
- Error handling and edge cases
- Complete auth flow integration tests
📂 Project Structure
src/ → main source code
├── index.js → CommonJS version
└── index.mjs → ES Module version
test/ → jest test suite
├── test-require.test.js → CommonJS tests
└── test-import.test.mjs → ES Module tests
examples/ → usage examples
├── example-require.js → CommonJS example
└── example-import.mjs → ES Module example
index.d.ts → TypeScript definitions📜 API
createToken(payload, secret, options?)
Create a new JWT token.
Parameters:
payload(object): The payload data to include in the tokensecret(string | Buffer): Secret key or private key for signingoptions(object, optional):algorithm(string): Algorithm to use -HS256,HS512,RS256,RS512,PS256,ES256(default:HS256)expiresIn(number): Token expiration time in seconds (default:3600)notBefore(number): Token not valid before this time offset in secondsaudience(string): Intended audience for the tokenissuer(string): Token issuersubject(string): Token subject
Returns: Promise resolving to the signed JWT token string
Example:
const token = await createToken({ userId: 123, role: "admin" }, "secret-key", {
algorithm: "HS256",
expiresIn: 7200, // 2 hours
issuer: "my-app",
audience: "my-api",
subject: "user-authentication",
});verifyToken(token, secret, options?)
Verify and validate a JWT token.
Parameters:
token(string): The JWT token to verifysecret(string | Buffer): Secret key or public key for verificationoptions(object, optional):ignoreExpiration(boolean): If true, ignores token expiration (default:false)audience(string): Expected audience to validate againstissuer(string): Expected issuer to validate against
Returns: Promise resolving to the verified token payload
Throws:
JsonWebTokenError: Invalid token format, signature, audience, issuer, or revoked tokenTokenExpiredError: Token has expiredNotBeforeError: Token is not yet valid (nbf claim)
Example:
try {
const payload = await verifyToken(token, "secret-key", {
audience: "my-api",
issuer: "my-app",
});
console.log("User ID:", payload.userId);
} catch (error) {
if (error instanceof TokenExpiredError) {
console.log("Token expired!");
} else if (error instanceof NotBeforeError) {
console.log("Token not yet valid!");
} else {
console.log("Invalid token:", error.message);
}
}decodeToken(token, options?)
Decode a JWT token without verifying its signature.
Parameters:
token(string): The JWT token to decodeoptions(object, optional):complete(boolean): If true, returns both header and payload; if false, returns only payload (default:false)
Returns: The decoded token payload, or { header, payload } if complete is true
Throws:
JsonWebTokenError: Invalid token format
Example:
// Decode payload only
const payload = decodeToken(token);
console.log(payload); // { userId: 123, iat: 1234567890, exp: 1234571490, ... }
// Decode with complete info
const { header, payload } = decodeToken(token, { complete: true });
console.log(header); // { alg: 'HS256', typ: 'JWT' }
console.log(payload); // { userId: 123, ... }revokeToken(token)
Revoke a JWT token by adding it to the blacklist.
Parameters:
token(string): The JWT token to revoke
Returns: Promise
Description: Adds the token's jti (JWT ID) to a blacklist cache. The token will remain blacklisted until its expiration time. Uses simple-cache-id for efficient in-memory blacklist management.
Example:
// Revoke a token (e.g., on user logout)
await revokeToken(token);
// Subsequent verification attempts will fail
await verifyToken(token, SECRET); // Throws: "Token revoked"createTokenPair(payload, secret, options?)
Creates a pair of access and refresh tokens.
Parameters:
payload(object): The payload data to include in both tokenssecret(string | Buffer): Secret key or private key for signingoptions(object, optional):algorithm(string): Algorithm to use (default:HS256)accessExpiresIn(number): Access token expiration in seconds (default:900- 15 minutes)refreshExpiresIn(number): Refresh token expiration in seconds (default:604800- 7 days)accessAudience(string): Audience for access token (default:api)refreshAudience(string): Audience for refresh token (default:refresh)issuer(string): Token issuersubject(string): Token subject
Returns: Promise resolving to { accessToken, refreshToken }
Example:
const { accessToken, refreshToken } = await createTokenPair(
{ userId: 123, role: "admin" },
"secret-key",
{
accessExpiresIn: 900, // 15 minutes
refreshExpiresIn: 604800, // 7 days
issuer: "auth-service",
}
);refreshTokens(refreshToken, secret, options?)
Refreshes tokens using a valid refresh token.
Parameters:
refreshToken(string): The refresh token to usesecret(string | Buffer): Secret key or public key for verificationoptions(object, optional):algorithm(string): Algorithm for new tokens (default:HS256)accessExpiresIn(number): New access token expiration (default:900)refreshExpiresIn(number): New refresh token expiration (default:604800)accessAudience(string): Audience for access token (default:api)refreshAudience(string): Expected audience for validation (default:refresh)issuer(string): Expected issuer to validaterotateRefreshToken(boolean): Whether to generate new refresh token (default:true)
Returns: Promise resolving to { accessToken, refreshToken? }
Throws:
JsonWebTokenError: Invalid or revoked refresh tokenTokenExpiredError: Refresh token has expiredNotBeforeError: Refresh token not yet valid
Example:
// With rotation (default) - returns both tokens
const { accessToken, refreshToken } = await refreshTokens(
oldRefreshToken,
"secret-key"
);
// Without rotation - returns only access token
const { accessToken } = await refreshTokens(oldRefreshToken, "secret-key", {
rotateRefreshToken: false,
});🔐 Algorithms
HMAC Algorithms (Symmetric)
- HS256 - HMAC using SHA-256 (default, recommended for most use cases)
- HS512 - HMAC using SHA-512 (more secure, slightly slower)
Usage:
const token = await createToken(payload, "your-secret-key", {
algorithm: "HS256",
});RSA Algorithms (Asymmetric)
- RS256 - RSASSA-PKCS1-v1_5 using SHA-256
- RS512 - RSASSA-PKCS1-v1_5 using SHA-512
- PS256 - RSASSA-PSS using SHA-256
Usage:
const fs = require("fs");
const privateKey = fs.readFileSync("private.pem");
const publicKey = fs.readFileSync("public.pem");
// Sign with private key
const token = await createToken(payload, privateKey, {
algorithm: "RS256",
});
// Verify with public key
const payload = await verifyToken(token, publicKey);ECDSA Algorithm (Asymmetric)
- ES256 - ECDSA using P-256 and SHA-256
Usage:
const token = await createToken(payload, privateKey, {
algorithm: "ES256",
});🔑 JWT Claims
Standard Claims (Automatically Managed)
jti(JWT ID) - Unique token identifier (auto-generated)iat(Issued At) - Timestamp when token was created (auto-generated)exp(Expiration Time) - Timestamp when token expires (set viaexpiresIn)
Optional Claims
nbf(Not Before) - Token not valid before this timestamp (set vianotBefore)aud(Audience) - Intended recipient of the token (set viaaudience)iss(Issuer) - Token issuer (set viaissuer)sub(Subject) - Subject of the token (set viasubject)
Example:
const token = await createToken({ userId: 123 }, SECRET, {
expiresIn: 3600, // exp = iat + 3600
notBefore: 60, // nbf = iat + 60
audience: "my-api",
issuer: "auth-service",
subject: "user-auth",
});
// Payload will contain:
// {
// userId: 123,
// jti: "a1b2c3d4e5f6g7h8",
// iat: 1234567890,
// exp: 1234571490,
// nbf: 1234567950,
// aud: "my-api",
// iss: "auth-service",
// sub: "user-auth"
// }🛡️ Error Handling
Error Classes
const {
JsonWebTokenError,
TokenExpiredError,
NotBeforeError,
} = require("simple-jwt-id");JsonWebTokenError- Base error class for all JWT errors- Invalid token format
- Invalid signature
- Invalid audience
- Invalid issuer
- Token revoked
TokenExpiredErrorextendsJsonWebTokenError- Token has expired (current time > exp)
NotBeforeErrorextendsJsonWebTokenError- Token used before valid time (current time < nbf)
Error Handling Example
try {
const payload = await verifyToken(token, SECRET);
} catch (error) {
if (error instanceof TokenExpiredError) {
// Handle expired token - maybe refresh it
console.log("Token expired, please login again");
} else if (error instanceof NotBeforeError) {
// Handle token not yet valid
console.log("Token not yet valid, please wait");
} else if (error instanceof JsonWebTokenError) {
// Handle other JWT errors
console.log("Invalid token:", error.message);
} else {
// Handle unexpected errors
console.error("Unexpected error:", error);
}
}🗄️ Token Revocation
Token revocation is handled automatically using simple-cache-id for efficient in-memory blacklist management.
How it works:
- Each token has a unique
jti(JWT ID) claim - When you call
revokeToken(), thejtiis added to an in-memory blacklist - The blacklist entry expires when the token would naturally expire (based on
expclaim) - Verification checks the blacklist before validating the token
Benefits:
- No database required for basic revocation
- Automatic cleanup when tokens expire
- Memory-efficient (only stores JTI, not full token)
- Optional persistent storage support (via simple-cache-id)
Example Use Cases:
- User logout
- Password reset
- Permission changes
- Security incidents
// User logs out
await revokeToken(userToken);
// Change user permissions - revoke all existing tokens
await revokeToken(token1);
await revokeToken(token2);
await revokeToken(token3);
// Security incident - revoke compromised token
await revokeToken(compromisedToken);💡 TypeScript Support
TypeScript definitions are included out of the box!
import {
createToken,
verifyToken,
decodeToken,
revokeToken,
type JWTPayload,
type CreateTokenOptions,
type VerifyTokenOptions,
JsonWebTokenError,
TokenExpiredError,
NotBeforeError,
} from "simple-jwt-id";
interface UserPayload extends JWTPayload {
userId: number;
username: string;
role: string;
}
const payload: UserPayload = {
userId: 123,
username: "john_doe",
role: "admin",
};
const options: CreateTokenOptions = {
algorithm: "HS256",
expiresIn: 3600,
issuer: "my-app",
};
const token = await createToken(payload, "secret", options);
try {
const verified = await verifyToken(token, "secret");
console.log(verified.userId); // TypeScript knows this exists
} catch (error) {
if (error instanceof TokenExpiredError) {
console.log("Token expired!");
}
}📄 License
MIT © 2025
🙏 Acknowledgments
- Uses simple-cache-id for efficient token blacklist management
- Inspired by jsonwebtoken with a focus on simplicity and modern JavaScript
