Functional TypeScript JWT authentication library with refresh token support and built-in rate limiting
A production-ready, type-safe JWT authentication system with advanced security features like token rotation, concurrent usage detection, device fingerprinting, and comprehensive rate limiting protection.
- Secure by Default: Token rotation, concurrent usage detection
- Device Tracking: Optional device fingerprinting support
- Flexible Storage: Use any database with the storage interface
- Express.js Integration: Ready-to-use middleware
- Built-in Rate Limiting: IP-based request limiting
- Brute Force Protection: Account lockout after failed attempts
- IP Whitelist/Blacklist: Allow trusted IPs, block malicious ones
- Real-time Alerts: Webhook and email notifications
- Functional Design: Pure functions, immutable operations
- Full TypeScript: Complete type safety and IntelliSense support
- Production Ready: Built-in security features and error handling
- Test Friendly: Easy to test with dependency injection
npm install jwt-auth-managerRequired dependencies:
npm install jsonwebtoken
npm install -D @types/jsonwebtokenimport {
createAuthContext,
createMemoryStorage,
generateTokenPair,
refreshTokens,
createAuthMiddleware,
} from "jwt-auth-manager";
// Create storage (use createMemoryStorage for development)
const storage = createMemoryStorage();
// Create auth context
const authContext = createAuthContext(
{
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
accessTokenExpiry: "15m",
refreshTokenExpiry: "7d",
},
storage
);
// Generate tokens
const user = { id: "user123", email: "[email protected]" };
const tokens = await generateTokenPair(user, authContext);
console.log(tokens);
// { accessToken: "eyJhbGc...", refreshToken: "eyJhbGc..." }
// Refresh expired tokens
const newTokens = await refreshTokens(tokens.refreshToken, authContext);import express from "express";
const app = express();
const authenticateToken = createAuthMiddleware(authContext);
// Protected route
app.get("/profile", authenticateToken, (req: any, res) => {
res.json({ user: req.user });
});import {
createRateLimitContext,
createMemoryRateLimitStorage,
createRateLimitPipeline,
} from "jwt-auth-manager";
// Create rate limiting context
const rateLimitStorage = createMemoryRateLimitStorage();
const rateLimitContext = createRateLimitContext(
{
maxAttempts: 5, // 5 attempts per window
windowMs: 15 * 60 * 1000, // 15 minutes window
blockDurationMs: 60 * 60 * 1000, // 1 hour block
},
rateLimitStorage
);
// Apply rate limiting to login endpoint
app.post(
"/login",
...createRateLimitPipeline(rateLimitContext),
async (req, res) => {
// Your login logic here
}
);const authContext = createAuthContext(
{
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
accessTokenExpiry: "15m", // or 900 (seconds)
refreshTokenExpiry: "7d", // or 604800 (seconds)
},
storage,
{
enableTokenRotation: true, // Rotate tokens on refresh
enableConcurrentUsageDetection: true, // Detect token reuse
enableDeviceFingerprinting: false, // Track devices
enableLocationTracking: false, // Track IP/location
maxConcurrentTokens: 5, // Max tokens per user
}
);const deviceInfo = {
fingerprint: req.headers["x-device-fingerprint"] as string,
ipAddress: req.ip,
userAgent: req.headers["user-agent"],
};
// Generate tokens with device info
const tokens = await generateTokenPair(user, authContext, deviceInfo);
// Refresh with device verification
const newTokens = await refreshTokens(
tokens.refreshToken,
authContext,
deviceInfo
);Every refresh operation generates new access and refresh tokens, invalidating the old ones.
// Old refresh token becomes invalid after use
const newTokens = await refreshTokens(oldRefreshToken, authContext);
// oldRefreshToken is now blacklistedIf the same refresh token is used multiple times, all user tokens are invalidated.
// First usage - OK
const tokens1 = await refreshTokens(refreshToken, authContext);
// Second usage of same token - Security violation!
// All user tokens will be invalidated
try {
const tokens2 = await refreshTokens(refreshToken, authContext);
} catch (error) {
console.log(error.message); // "Concurrent token usage detected"
}Tokens can be tied to specific devices using fingerprints.
const deviceInfo = { fingerprint: "unique_device_id" };
const tokens = await generateTokenPair(user, authContext, deviceInfo);
// This will fail if used from a different device
await refreshTokens(tokens.refreshToken, authContext, {
fingerprint: "different_device",
});import {
createRateLimitContext,
createMemoryRateLimitStorage,
createRateLimitPipeline,
} from "jwt-auth-manager";
// Create rate limit context
const storage = createMemoryRateLimitStorage();
const rateLimitContext = createRateLimitContext(
{
maxAttempts: 5, // 5 attempts per window
windowMs: 15 * 60 * 1000, // 15 minutes window
blockDurationMs: 60 * 60 * 1000, // 1 hour block
},
storage
);
// Apply to Express.js app
const rateLimitPipeline = createRateLimitPipeline(rateLimitContext);
app.use(...rateLimitPipeline);const rateLimitContext = createRateLimitContext(
{
// Basic rate limiting
maxAttempts: 5,
windowMs: 15 * 60 * 1000,
blockDurationMs: 60 * 60 * 1000,
skipSuccessfulRequests: false,
// IP Security
whitelist: ["192.168.1.0/24"], // Trusted IPs
blacklist: ["10.0.0.0/8"], // Blocked IPs
// Brute force protection
bruteForce: {
enabled: true,
maxFailedAttempts: 10, // Lock account after 10 failures
lockoutDurationMs: 2 * 60 * 60 * 1000, // 2 hours lockout
resetCountOnSuccess: true, // Reset counter on successful login
},
// Security alerts
alerts: {
enabled: true,
threshold: 3, // Alert after 3 attempts
webhook: "https://hooks.slack.com/webhook/your-webhook",
email: "[email protected]",
},
},
storage
);For custom endpoints or non-middleware usage:
import { checkRateLimit, recordAttempt } from "jwt-auth-manager";
app.post("/custom-endpoint", async (req, res) => {
const identifier = req.ip;
const userId = req.body.userId;
// Check rate limit
const rateLimitResult = await checkRateLimit(
identifier,
rateLimitContext,
userId
);
if (!rateLimitResult.allowed) {
return res.status(429).json({
error: "Too Many Requests",
message: rateLimitResult.reason,
retryAfter: rateLimitResult.retryAfter,
});
}
try {
// Your business logic
const result = await processRequest(req.body);
// Record successful attempt
await recordAttempt(identifier, rateLimitContext, true, userId);
res.json(result);
} catch (error) {
// Record failed attempt
await recordAttempt(identifier, rateLimitContext, false, userId);
res.status(500).json({ error: "Processing failed" });
}
});import { unlockUser, unlockIP, getRateLimitStatus } from "jwt-auth-manager";
// Unlock locked user account
app.post("/admin/unlock-user/:userId", async (req, res) => {
await unlockUser(req.params.userId, rateLimitContext);
res.json({ message: "User account unlocked" });
});
// Unlock blocked IP address
app.post("/admin/unlock-ip/:ip", async (req, res) => {
await unlockIP(req.params.ip, rateLimitContext);
res.json({ message: "IP address unlocked" });
});
// Get rate limit status
app.get("/admin/rate-limit-status/:identifier", async (req, res) => {
const status = await getRateLimitStatus(
req.params.identifier,
rateLimitContext,
req.query.userId as string
);
res.json(status);
});The middleware automatically adds rate limiting headers:
X-RateLimit-Limit: 5
X-RateLimit-Remaining: 3
X-RateLimit-Reset: 2024-01-15T10:30:00Z
Retry-After: 3600
The library uses storage interfaces that you can implement for any database.
import {
createMemoryStorage,
createMemoryRateLimitStorage,
} from "jwt-auth-manager";
// JWT token storage
const jwtStorage = createMemoryStorage();
// Perfect for development and testing
// Rate limiting storage
const rateLimitStorage = createMemoryRateLimitStorage();
// Good for development and testingimport { TokenStorage, RefreshTokenData } from "jwt-auth-manager";
const createCustomStorage = (): TokenStorage => ({
async saveRefreshToken(data: Omit<RefreshTokenData, "id">): Promise<string> {
// Save to your database
return "token_id";
},
async getRefreshToken(token: string): Promise<RefreshTokenData | null> {
// Fetch from your database
return null;
},
async invalidateRefreshToken(token: string): Promise<void> {
// Delete from your database
},
async invalidateAllUserTokens(userId: string | number): Promise<void> {
// Delete all user tokens
},
async markTokenAsUsed(token: string): Promise<void> {
// Mark token as used (for concurrent usage detection)
},
async cleanupExpiredTokens(): Promise<void> {
// Clean up expired tokens
},
});import { RateLimitStorage } from "jwt-auth-manager";
const createCustomRateLimitStorage = (): RateLimitStorage => ({
async getRateLimitEntry(key: string) {
// Your implementation
},
async saveRateLimitEntry(key: string, entry: RateLimitEntry, ttl: number) {
// Your implementation
},
// ... implement all required methods
});We provide complete storage implementations in the examples folder:
// Copy from examples/mongodbStorage.ts
import { createMongoDBStorage } from "./examples/mongodbStorage";
import { MongoClient } from "mongodb";
const client = new MongoClient("mongodb://localhost:27017");
await client.connect();
const db = client.db("auth_app");
const storage = createMongoDBStorage(db);// Copy from examples/redisStorage.ts
import { createRedisStorage } from "./examples/redisStorage";
import { createClient } from "redis";
const client = createClient();
await client.connect();
const storage = createRedisStorage(client);// Copy from examples/postgresStorage.ts
import { createPostgreSQLStorage } from "./examples/postgresStorage";
import { Pool } from "pg";
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const storage = createPostgreSQLStorage(pool);import express from "express";
import {
createAuthContext,
createMemoryStorage,
generateTokenPair,
refreshTokens,
createAuthMiddleware,
logoutUser,
logoutDevice,
createRateLimitContext,
createMemoryRateLimitStorage,
createRateLimitPipeline,
} from "jwt-auth-manager";
const app = express();
app.use(express.json());
// JWT Authentication setup
const storage = createMemoryStorage();
const authContext = createAuthContext(
{
accessTokenSecret: process.env.ACCESS_TOKEN_SECRET!,
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET!,
accessTokenExpiry: "15m",
refreshTokenExpiry: "7d",
},
storage,
{
enableTokenRotation: true,
enableConcurrentUsageDetection: true,
enableDeviceFingerprinting: true,
}
);
// Rate Limiting setup
const rateLimitStorage = createMemoryRateLimitStorage();
const rateLimitContext = createRateLimitContext(
{
maxAttempts: 5,
windowMs: 15 * 60 * 1000,
blockDurationMs: 60 * 60 * 1000,
bruteForce: {
enabled: true,
maxFailedAttempts: 10,
lockoutDurationMs: 2 * 60 * 60 * 1000,
},
alerts: {
enabled: true,
threshold: 3,
webhook: process.env.SECURITY_WEBHOOK,
},
},
rateLimitStorage
);
// Middleware
const authenticateToken = createAuthMiddleware(authContext);
// Helper function
const extractDeviceInfo = (req: express.Request) => ({
fingerprint: req.headers["x-device-fingerprint"] as string,
ipAddress: req.ip,
userAgent: req.headers["user-agent"],
});
// Rate limited + authenticated login endpoint
app.post(
"/login",
// Apply rate limiting pipeline
...createRateLimitPipeline(rateLimitContext),
async (req, res) => {
try {
// 1. Validate input
const { email, password } = req.body;
if (!email || !password) {
return res.status(400).json({ error: "Email and password required" });
}
// 2. Authenticate user
const user = await authenticateUser(req.body.email, req.body.password);
const deviceInfo = extractDeviceInfo(req);
// 3. Generate JWT tokens
const tokens = await generateTokenPair(user, authContext, deviceInfo);
// 4. Success response
res.json({
message: "Login successful",
user: { id: user.id, email: user.email },
...tokens,
});
} catch (error) {
// Rate limiting middleware automatically records this as a failed attempt
res.status(401).json({
error: error instanceof Error ? error.message : "Authentication failed",
});
}
}
);
app.post("/refresh", async (req, res) => {
try {
const { refreshToken } = req.body;
const deviceInfo = extractDeviceInfo(req);
const newTokens = await refreshTokens(
refreshToken,
authContext,
deviceInfo
);
res.json(newTokens);
} catch (error) {
res.status(401).json({
error: error instanceof Error ? error.message : "Token refresh failed",
});
}
});
app.get("/profile", authenticateToken, (req: any, res) => {
res.json({ user: req.user });
});
app.post("/logout", async (req, res) => {
try {
const { refreshToken, logoutAll } = req.body;
if (logoutAll) {
// Logout from all devices
const decoded = jwt.verify(
refreshToken,
process.env.REFRESH_TOKEN_SECRET!
);
await logoutUser(decoded.userId, authContext);
} else {
// Logout from current device only
await logoutDevice(refreshToken, authContext);
}
res.json({ message: "Logged out successfully" });
} catch (error) {
res.status(500).json({
error: error instanceof Error ? error.message : "Logout failed",
});
}
});
app.listen(3000, () => {
console.log("π Server running on port 3000");
});Creates the authentication context with configuration and storage.
Generates access and refresh token pair for a user.
Refreshes expired access token using refresh token.
Verifies and decodes access token.
Invalidates all tokens for a specific user.
Invalidates specific refresh token (single device logout).
Creates rate limiting context with configuration and storage.
Checks if request should be rate limited.
Records an attempt (success or failure).
Manually unlocks a user account.
Manually unlocks an IP address.
Creates Express.js middleware for protecting routes.
Creates Express.js rate limiting middleware pipeline.
Creates in-memory storage for JWT tokens (development/testing).
Creates in-memory storage for rate limiting (development/testing).
The functional design makes testing straightforward:
import {
createAuthContext,
createMemoryStorage,
generateTokenPair,
verifyAccessToken,
createRateLimitContext,
createMemoryRateLimitStorage,
checkRateLimit,
} from "jwt-auth-manager";
describe("JWT Auth Manager", () => {
const storage = createMemoryStorage();
const authContext = createAuthContext(
{
accessTokenSecret: "test-secret",
refreshTokenSecret: "test-refresh-secret",
},
storage
);
it("should generate valid tokens", async () => {
const user = { id: "test-user", email: "[email protected]" };
const tokens = await generateTokenPair(user, authContext);
expect(tokens.accessToken).toBeDefined();
expect(tokens.refreshToken).toBeDefined();
const result = verifyAccessToken(tokens.accessToken, authContext);
expect(result.success).toBe(true);
expect(result.data.userId).toBe("test-user");
});
});
describe("Rate Limiting", () => {
const rateLimitStorage = createMemoryRateLimitStorage();
const rateLimitContext = createRateLimitContext(
{
maxAttempts: 3,
windowMs: 60 * 1000,
blockDurationMs: 60 * 1000,
},
rateLimitStorage
);
it("should allow requests within limit", async () => {
const result = await checkRateLimit("127.0.0.1", rateLimitContext);
expect(result.allowed).toBe(true);
expect(result.remaining).toBe(2);
});
});# Required
ACCESS_TOKEN_SECRET=your-super-secret-access-key-min-32-chars
REFRESH_TOKEN_SECRET=your-super-secret-refresh-key-min-32-chars
# Optional
ACCESS_TOKEN_EXPIRY=15m
REFRESH_TOKEN_EXPIRY=7d
# Security (Optional)
SECURITY_WEBHOOK=https://hooks.slack.com/webhook/your-webhook- Strong Secrets: Use cryptographically strong secrets (32+ characters)
- HTTPS Only: Always use HTTPS in production
- Secure Storage: Store refresh tokens securely (httpOnly cookies recommended)
- Short Access Token Lifetime: Keep access tokens short-lived (15-30 minutes)
- Token Rotation: Always enable token rotation in production
- Rate Limiting: Apply rate limiting to authentication endpoints
- Monitoring: Monitor for suspicious authentication patterns
- IP Security: Use whitelist/blacklist for known good/bad IPs
- Alert Systems: Set up real-time security alerts
- Database Indexing: Index token, userId, and expiresAt fields
- Connection Pooling: Use connection pooling for database storage
- TTL Indexes: Use TTL indexes for automatic token cleanup (MongoDB)
- Caching: Consider Redis for high-performance token storage
- Cleanup Jobs: Regularly clean up expired tokens
- Rate Limit Storage: Use Redis for distributed rate limiting
TypeScript errors with jwt.sign():
npm install @types/jsonwebtoken@latest"Invalid refresh token" errors:
- Check if token rotation is causing conflicts
- Verify refresh token hasn't been used before (concurrent usage detection)
- Ensure refresh token hasn't expired
Memory storage losing data:
- Memory storage is cleared on app restart
- Use persistent storage (MongoDB, PostgreSQL, etc.) for production
Rate limiting not working:
- Check if rate limiting storage is properly configured
- Verify middleware order in Express.js
- Ensure proper IP extraction from requests
The library was rewritten with functional approach:
// Old (v0.x)
const jwtManager = new JWTManager(config, storage, options);
await jwtManager.generateTokens(user);
// New (v1.0)
const authContext = createAuthContext(config, storage, options);
await generateTokenPair(user, authContext);Breaking Changes:
JWTManagerclass removed β UsecreateAuthContext()+ functionsgenerateTokens()βgenerateTokenPair()refreshToken()βrefreshTokens()- All functions are now pure and take
authContextas parameter
We welcome contributions! Please see our Contributing Guide for details.
# Clone the repository
git clone https://github.com/birkan-dogan/jwt-auth-manager.git
cd jwt-auth-manager
# Install dependencies
npm install
# Run tests
npm test
# Build the project
npm run build
# Run in development mode
npm run devsrc/
βββ core/ # Core authentication functions
β βββ tokenGeneration.ts
β βββ tokenVerification.ts
β βββ tokenRefresh.ts
β βββ tokenInvalidation.ts
βββ security/ # Rate limiting & security features
β βββ rateLimiting.ts
βββ middleware/ # Express.js middleware
β βββ authMiddleware.ts
βββ storage/ # Storage implementations
β βββ memoryStorage.ts
βββ utils/ # Utility functions
β βββ helpers.ts
βββ types/ # TypeScript type definitions
β βββ index.ts
βββ index.ts # Main exports
examples/ # Database storage examples
βββ mongodbStorage.ts
βββ postgresStorage.ts
βββ redisStorage.ts
βββ README.md
- π Initial release with functional architecture
- β¨ Token rotation and concurrent usage detection
- π Device fingerprinting support
- π‘οΈ Built-in rate limiting and brute force protection
- π Full TypeScript support
- π Express.js middleware
- ποΈ Flexible storage interface
- π Complete documentation and examples
A: Yes! The core functions are framework-agnostic. Only the middleware is Express-specific, but you can easily create similar middleware for other frameworks.
A: Here's a React example with Axios interceptors:
import axios from "axios";
// Response interceptor for automatic token refresh
axios.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
try {
const refreshToken = localStorage.getItem("refreshToken");
const response = await axios.post("/refresh", { refreshToken });
localStorage.setItem("accessToken", response.data.accessToken);
localStorage.setItem("refreshToken", response.data.refreshToken);
// Retry original request
return axios.request(error.config);
} catch (refreshError) {
// Refresh failed, redirect to login
window.location.href = "/login";
}
}
return Promise.reject(error);
}
);A:
- Access Token: Short-lived (15-30 min), used for API requests
- Refresh Token: Long-lived (7-30 days), used only to get new access tokens
A: Use longer refresh token expiry:
const authContext = createAuthContext(
{
// ... other config
refreshTokenExpiry: rememberMe ? "30d" : "7d",
},
storage
);A: Use a shared storage like Redis for rate limiting:
// Use Redis for distributed rate limiting
const redisClient = createClient();
const rateLimitStorage = createRedisRateLimitStorage(redisClient);A: Yes, modify the generateTokenPair function or extend it:
const generateCustomTokenPair = async (
user: User,
authContext: AuthContext,
customData: any
) => {
// Add custom data to user object
const extendedUser = { ...user, ...customData };
return generateTokenPair(extendedUser, authContext);
};- Passport.js - Authentication middleware for Node.js
- Auth0 - Identity platform for developers
- Firebase Auth - Google's authentication solution
- NextAuth.js - Authentication for Next.js
MIT License - see the LICENSE file for details.
- β Star this repo if you find it helpful
- π Report bugs via GitHub Issues
- π¬ Ask questions in GitHub Discussions
- π§ Email: [email protected]
- Built-in rate limiting support
- WebSocket authentication support
- OAuth2 integration helpers
- Multi-tenant support
- Token blacklisting with Redis
- Audit logging capabilities
- GraphQL middleware support
- React hooks package
- Vue.js composables package
- CLI tool for key generation
Documentation β’ Examples β’ Contributing β’ Changelog