@soldead/lockify
v1.0.2
Published
A lightweight, framework-agnostic authentication package for Node.js with bcrypt and JWT helpers
Maintainers
Readme
Lockify 🔐
A lightweight, framework-agnostic authentication package for Node.js with bcrypt and JWT helpers. Designed to be simple, secure, and compatible with all Node.js frameworks.
Features
- 🔒 Secure password hashing with bcrypt
- 🎫 JWT token generation and validation
- 🌐 Framework-agnostic - works with Express, Koa, Fastify, and more
- 📝 Full TypeScript support with type definitions
- 🛡️ Custom error handling for better debugging
- ⚙️ Advanced JWT options (expiration, issuer, audience, etc.)
- 🧪 Thoroughly tested with Jest
- 📦 Zero configuration - works out of the box
Installation
npm install @soldead/lockifyQuick Start
import {
hashPassword,
comparePassword,
generateToken,
verifyToken,
requireAuth,
optionalAuth,
requireRole,
validatePassword,
generateSalt
} from 'lockify';
// Hash a password
const hashedPassword = await hashPassword('mySecretPassword');
// Compare password
const isValid = await comparePassword('mySecretPassword', hashedPassword);
// Generate JWT token
const token = generateToken({ userId: 123, role: 'user' }, 'your-secret-key');
// Verify JWT token
const decoded = verifyToken(token, 'your-secret-key');API Reference
Password Helpers
hashPassword(password: string): Promise<string>
Hashes a password using bcrypt with a default salt rounds of 12.
const hashedPassword = await hashPassword('userPassword123');
console.log(hashedPassword); // $2b$12$...comparePassword(password: string, hash: string): Promise<boolean>
Compares a plain text password with a bcrypt hash.
const isValid = await comparePassword('userPassword123', hashedPassword);
console.log(isValid); // true or falsevalidatePassword(password: string, options?: PasswordValidationOptions): boolean
Validates password strength according to security requirements.
const isStrong = validatePassword('MySecurePassword123!', {
minLength: 8,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSymbols: true
});
console.log(isStrong); // true or falsegenerateSalt(rounds?: number): Promise<string>
Generates a random salt for bcrypt hashing.
const salt = await generateSalt(12);
console.log(salt); // $2b$12$...JWT Helpers
generateToken(payload: JwtPayload, secret: string, options?: JwtOptions): string
Generates a JWT token with the given payload and secret.
// Basic usage
const token = generateToken({ userId: 123 }, 'your-secret-key');
// With options
const token = generateToken(
{ userId: 123, role: 'admin' },
'your-secret-key',
{
expiresIn: '24h',
issuer: 'your-app-name',
audience: 'your-users'
}
);verifyToken(token: string, secret: string): JwtPayload
Verifies and decodes a JWT token. Throws an error if the token is invalid or expired.
try {
const decoded = verifyToken(token, 'your-secret-key');
console.log('User ID:', decoded.userId);
} catch (error) {
console.log('Token verification failed:', error.message);
}decodeToken(token: string): JwtPayload | null
Decodes a JWT token without verification (unsafe). Returns null if the token is malformed.
const decoded = decodeToken(token);
if (decoded) {
console.log('Token payload:', decoded);
}isTokenExpired(token: string): boolean
Checks if a JWT token is expired.
const expired = isTokenExpired(token);
console.log('Token expired:', expired);getTokenExpiration(token: string): Date | null
Gets the expiration date of a JWT token.
const expiration = getTokenExpiration(token);
if (expiration) {
console.log('Token expires at:', expiration);
}refreshToken(token: string, secret: string, options?: JwtOptions): string
Refreshes an existing token with a new expiration time.
const newToken = refreshToken(oldToken, 'your-secret-key', {
expiresIn: '24h'
});Authentication Middleware
requireAuth(getUserById: GetUserById, secret: string): MiddlewareFunction
Creates a middleware function that requires authentication for accessing protected routes.
type GetUserById = (id: string) => Promise<any>;
const authMiddleware = requireAuth(
async (id: string) => {
// Your user lookup logic
return await db.users.findById(id);
},
'your-jwt-secret'
);optionalAuth(getUserById: GetUserById, secret: string): MiddlewareFunction
Creates an optional authentication middleware that adds user information to the request if a valid token is provided, but doesn't fail if no token is present.
const optionalAuthMiddleware = optionalAuth(
async (id: string) => {
return await db.users.findById(id);
},
'your-jwt-secret'
);requireRole(getUserById: GetUserById, secret: string, allowedRoles: string[]): MiddlewareFunction
Creates a role-based authentication middleware that checks if the authenticated user has the required role(s).
const adminMiddleware = requireRole(
async (id: string) => {
return await db.users.findById(id);
},
'your-jwt-secret',
['admin', 'superuser']
);Framework Integration Examples
Express.js
import express from 'express';
import { requireAuth, generateToken, hashPassword, comparePassword } from 'lockify';
const app = express();
app.use(express.json());
// User lookup function
const getUserById = async (id: string) => {
// Replace with your actual user lookup logic
return await db.users.findById(id);
};
// Create auth middleware
const authMiddleware = requireAuth(getUserById, process.env.JWT_SECRET!);
// Public route - Login
app.post('/login', async (req, res) => {
try {
const { email, password } = req.body;
// Find user by email
const user = await db.users.findByEmail(email);
if (!user) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Verify password
const isValid = await comparePassword(password, user.password);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Generate token
const token = generateToken(
{ userId: user.id, email: user.email },
process.env.JWT_SECRET!,
{ expiresIn: '24h' }
);
res.json({ token, user: { id: user.id, email: user.email } });
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
// Public route - Register
app.post('/register', async (req, res) => {
try {
const { email, password } = req.body;
// Hash password
const hashedPassword = await hashPassword(password);
// Create user
const user = await db.users.create({
email,
password: hashedPassword
});
res.status(201).json({ message: 'User created successfully' });
} catch (error) {
res.status(500).json({ error: 'Internal server error' });
}
});
// Protected route
app.get('/profile', authMiddleware, (req, res) => {
// req.user is available here
res.json({ user: req.user });
});
app.listen(3000, () => {
console.log('Server running on port 3000');
});Koa.js
import Koa from 'koa';
import Router from 'koa-router';
import { requireAuth } from 'lockify';
const app = new Koa();
const router = new Router();
// User lookup function
const getUserById = async (id: string) => {
return await db.users.findById(id);
};
// Create auth middleware
const authMiddleware = requireAuth(getUserById, process.env.JWT_SECRET!);
// Convert to Koa middleware
const koaAuthMiddleware = async (ctx: Koa.Context, next: Koa.Next) => {
await new Promise((resolve, reject) => {
authMiddleware(ctx.request, ctx.response, (err?: any) => {
if (err) reject(err);
else resolve(void 0);
});
});
await next();
};
// Protected route
router.get('/profile', koaAuthMiddleware, async (ctx) => {
ctx.body = { user: ctx.request.user };
});
app.use(router.routes());
app.listen(3000);Fastify
import fastify from 'fastify';
import { requireAuth } from 'lockify';
const app = fastify();
// User lookup function
const getUserById = async (id: string) => {
return await db.users.findById(id);
};
// Create auth middleware
const authMiddleware = requireAuth(getUserById, process.env.JWT_SECRET!);
// Register as Fastify plugin
app.register(async (fastify) => {
fastify.addHook('preHandler', async (request, reply) => {
await new Promise((resolve, reject) => {
authMiddleware(request.raw, reply.raw, (err?: any) => {
if (err) reject(err);
else resolve(void 0);
});
});
});
fastify.get('/profile', async (request, reply) => {
return { user: (request.raw as any).user };
});
});
app.listen({ port: 3000 });TypeScript Support
Lockify is written in TypeScript and provides full type definitions:
import {
JwtPayload,
JwtOptions,
GetUserById,
AuthError,
HashOptions,
PasswordValidationOptions
} from 'lockify';
// Custom JWT payload
interface CustomPayload extends JwtPayload {
userId: number;
role: 'admin' | 'user';
permissions: string[];
}
// Strongly typed user lookup
const getUserById: GetUserById = async (id: string) => {
const user = await db.users.findById(id);
if (!user) throw new AuthError('User not found');
return user;
};
// Generate token with custom payload
const token = generateToken<CustomPayload>(
{
userId: 123,
role: 'admin',
permissions: ['read', 'write']
},
'secret'
);
// Hash password with custom options
const hash = await hashPassword('password', { saltRounds: 14 });
// Validate password with custom rules
const isValid = validatePassword('password', {
minLength: 12,
requireUppercase: true,
requireLowercase: true,
requireNumbers: true,
requireSymbols: true
});Error Handling
Lockify provides custom error classes for better error handling:
import {
AuthError,
TokenError,
HashError,
InvalidTokenError,
ExpiredTokenError,
MissingTokenError,
MalformedTokenError,
UserNotFoundError,
InvalidPasswordError,
WeakPasswordError
} from 'lockify';
try {
const decoded = verifyToken(token, secret);
} catch (error) {
if (error instanceof ExpiredTokenError) {
console.log('Token has expired:', error.message);
} else if (error instanceof InvalidTokenError) {
console.log('Invalid token:', error.message);
} else if (error instanceof TokenError) {
console.log('Token error:', error.message);
} else if (error instanceof AuthError) {
console.log('Auth error:', error.message);
}
}Error Types
AuthError: General authentication errorsTokenError: JWT token related errorsHashError: Password hashing related errorsInvalidTokenError: Invalid token format or signatureExpiredTokenError: Token has expiredMissingTokenError: No token providedMalformedTokenError: Token format is invalidUserNotFoundError: User lookup failedInvalidPasswordError: Password validation failedWeakPasswordError: Password doesn't meet security requirements
Advanced Configuration
JWT Options
interface JwtOptions {
expiresIn?: string | number;
issuer?: string;
audience?: string | string[];
subject?: string;
algorithm?:
| 'HS256' | 'HS384' | 'HS512'
| 'RS256' | 'RS384' | 'RS512'
| 'PS256' | 'PS384' | 'PS512'
| 'ES256' | 'ES384' | 'ES512'
| 'none';
keyid?: string;
noTimestamp?: boolean;
header?: { [key: string]: unknown };
encoding?: string;
}
interface HashOptions {
saltRounds?: number;
}
interface PasswordValidationOptions {
minLength?: number;
maxLength?: number;
requireUppercase?: boolean;
requireLowercase?: boolean;
requireNumbers?: boolean;
requireSymbols?: boolean;
forbiddenPasswords?: string[];
}Custom Salt Rounds for Bcrypt
import { hashPassword } from 'lockify';
// Default salt rounds (12)
const hash1 = await hashPassword('password');
// Custom salt rounds
const hash2 = await hashPassword('password', { saltRounds: 14 });Best Practices
- Environment Variables: Always store JWT secrets in environment variables
- Token Expiration: Set appropriate expiration times for tokens
- Error Handling: Always handle authentication errors gracefully
- HTTPS: Use HTTPS in production to protect tokens in transit
- Refresh Tokens: Implement refresh token mechanism for better security
// Good practice example
const JWT_SECRET = process.env.JWT_SECRET;
const JWT_EXPIRES_IN = process.env.JWT_EXPIRES_IN || '24h';
if (!JWT_SECRET) {
throw new Error('JWT_SECRET environment variable is required');
}
const token = generateToken(
{ userId: user.id },
JWT_SECRET,
{ expiresIn: JWT_EXPIRES_IN }
);Utility Functions
Lockify provides additional utility functions for enhanced security and validation:
Header Utilities
import {
extractTokenFromHeader,
extractBearerToken,
validateAuthHeader,
extractTokenByScheme,
extractTokenFromCookie
} from 'lockify';
// Extract token from Authorization header
const token = extractTokenFromHeader('Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');
// Extract specifically Bearer tokens
const bearerToken = extractBearerToken('Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...');
// Extract token by custom scheme
const customToken = extractTokenByScheme('Custom eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', 'Custom');
// Extract token from cookie string
const cookieToken = extractTokenFromCookie('auth_token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...', 'auth_token');Validation Utilities
import {
validatePasswordStrength,
validateEmail,
validateJwtSecret,
sanitizeInput,
isPasswordForbidden
} from 'lockify';
// Validate password strength
const isStrong = validatePasswordStrength('MyPassword123!');
// Validate email format
const isValidEmail = validateEmail('[email protected]');
// Validate JWT secret strength
const isSecureSecret = validateJwtSecret('your-super-secret-key');
// Sanitize user input
const clean = sanitizeInput('<script>alert("xss")</script>user input');
// Check if password is in forbidden list
const isForbidden = isPasswordForbidden('password123', ['password123', '123456']);Security Utilities
import {
generateSecureRandom,
generateJwtSecret,
hashSha256,
generateHmac,
verifyHmac,
constantTimeCompare,
generateUuid
} from 'lockify';
// Generate secure random bytes
const randomBytes = generateSecureRandom(32);
// Generate secure JWT secret
const jwtSecret = generateJwtSecret(64);
// Hash data with SHA-256
const hash = hashSha256('data to hash');
// Generate HMAC
const hmac = generateHmac('data', 'secret');
// Verify HMAC
const isValid = verifyHmac('data', 'secret', hmac);
// Constant-time string comparison
const isEqual = constantTimeCompare('string1', 'string2');
// Generate UUID
const uuid = generateUuid();Testing
# Run tests
npm test
# Run tests in watch mode
npm run test:watch
# Run tests with coverage
npm run test:coverageContributing
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add some amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
Development Setup
# Clone the repository
git clone https://github.com/yourusername/lockify.git
# Install dependencies
npm install
# Run in development mode
npm run dev
# Build the project
npm run build
# Run tests
npm testLicense
This project is licensed under the MIT License - see the LICENSE file for details.
Support
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 📖 Documentation: GitHub Wiki
Changelog
v1.0.0
- ✨ Enhanced Authentication System
- Password hashing with bcrypt and configurable salt rounds
- Advanced password validation with customizable strength requirements
- JWT token generation, verification, and management
- Token refresh functionality and expiration checking
- Multiple authentication middleware options (required, optional, role-based)
- 🛡️ Security Features
- Comprehensive error handling with specific error types
- Secure random generation and HMAC utilities
- Constant-time comparison functions
- Input sanitization and validation
- 🌐 Framework Compatibility
- Works with Express, Koa, Fastify, and other Node.js frameworks
- Framework-agnostic middleware design
- 📝 TypeScript Support
- Full type definitions for all functions and interfaces
- Generic support for custom JWT payloads
- Type-safe middleware functions
- 🧪 Testing & Quality
- Comprehensive test suite with Jest
- ESLint and Prettier configuration
- CI/CD pipeline integration
Made with ❤️ by the Lockify team
