simple-jwt-id
Version:
A lightweight JWT library with multiple algorithm support and token revocation
733 lines (549 loc) โข 18.9 kB
Markdown
# simple-jwt-id
[](https://www.npmjs.com/package/simple-jwt-id)
[](https://www.npmjs.com/package/simple-jwt-id)
[](LICENSE)
[](https://github.com/ibnushahraa/simple-jwt-id/actions)
[](https://github.com/ibnushahraa/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](https://www.npmjs.com/package/simple-cache-id) for blacklist)
---
## ๐ฆ Installation
```bash
npm install simple-jwt-id
```
---
## ๐ Usage
### Basic Usage (CommonJS)
```js
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
```js
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
```js
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)
```js
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
```js
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
```js
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: "user@example.com",
});
// 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
```bash
npm test
```
Jest 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 token
- `secret` (string | Buffer): Secret key or private key for signing
- `options` (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 seconds
- `audience` (string): Intended audience for the token
- `issuer` (string): Token issuer
- `subject` (string): Token subject
**Returns:** Promise resolving to the signed JWT token string
**Example:**
```js
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 verify
- `secret` (string | Buffer): Secret key or public key for verification
- `options` (object, optional):
- `ignoreExpiration` (boolean): If true, ignores token expiration (default: `false`)
- `audience` (string): Expected audience to validate against
- `issuer` (string): Expected issuer to validate against
**Returns:** Promise resolving to the verified token payload
**Throws:**
- `JsonWebTokenError`: Invalid token format, signature, audience, issuer, or revoked token
- `TokenExpiredError`: Token has expired
- `NotBeforeError`: Token is not yet valid (nbf claim)
**Example:**
```js
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 decode
- `options` (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:**
```js
// 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<void>
**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](https://www.npmjs.com/package/simple-cache-id) for efficient in-memory blacklist management.
**Example:**
```js
// 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 tokens
- `secret` (string | Buffer): Secret key or private key for signing
- `options` (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 issuer
- `subject` (string): Token subject
**Returns:** Promise resolving to `{ accessToken, refreshToken }`
**Example:**
```js
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 use
- `secret` (string | Buffer): Secret key or public key for verification
- `options` (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 validate
- `rotateRefreshToken` (boolean): Whether to generate new refresh token (default: `true`)
**Returns:** Promise resolving to `{ accessToken, refreshToken? }`
**Throws:**
- `JsonWebTokenError`: Invalid or revoked refresh token
- `TokenExpiredError`: Refresh token has expired
- `NotBeforeError`: Refresh token not yet valid
**Example:**
```js
// 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:**
```js
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:**
```js
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:**
```js
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 via `expiresIn`)
### Optional Claims
- **`nbf`** (Not Before) - Token not valid before this timestamp (set via `notBefore`)
- **`aud`** (Audience) - Intended recipient of the token (set via `audience`)
- **`iss`** (Issuer) - Token issuer (set via `issuer`)
- **`sub`** (Subject) - Subject of the token (set via `subject`)
**Example:**
```js
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
```js
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
- **`TokenExpiredError`** extends `JsonWebTokenError`
- Token has expired (current time > exp)
- **`NotBeforeError`** extends `JsonWebTokenError`
- Token used before valid time (current time < nbf)
### Error Handling Example
```js
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](https://www.npmjs.com/package/simple-cache-id) for efficient in-memory blacklist management.
**How it works:**
1. Each token has a unique `jti` (JWT ID) claim
2. When you call `revokeToken()`, the `jti` is added to an in-memory blacklist
3. The blacklist entry expires when the token would naturally expire (based on `exp` claim)
4. 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
```js
// 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!
```typescript
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](LICENSE) ยฉ 2025
---
## ๐ Acknowledgments
- Uses [simple-cache-id](https://www.npmjs.com/package/simple-cache-id) for efficient token blacklist management
- Inspired by [jsonwebtoken](https://www.npmjs.com/package/jsonwebtoken) with a focus on simplicity and modern JavaScript
---
## ๐ Resources
- [JWT.io](https://jwt.io/) - Learn more about JSON Web Tokens
- [RFC 7519](https://tools.ietf.org/html/rfc7519) - JWT Specification
- [RFC 7515](https://tools.ietf.org/html/rfc7515) - JWS Specification