@bhandari88/express-auth
v1.3.1
Published
Plug-and-play authentication handler for Express.js with TypeScript supporting email, username, phone, and social login
Downloads
14
Maintainers
Readme
@bhandari88/express-auth
A plug-and-play authentication handler for Express.js with TypeScript supporting multiple authentication methods including email, username, phone, and social login (Google, Facebook).
📦 Package
npm install @bhandari88/express-authPackage URL: https://www.npmjs.com/package/@bhandari88/express-auth
Example
*Example Application: https://github.com/manojsinghindiit/auth-boiler-example
Features
✅ Multiple Authentication Methods
- Email/Password
- Username/Password
- Phone/Password
- Social Login (Google, Facebook)
✅ Security Best Practices
- JWT-based authentication with access and refresh tokens
- Node.js crypto (scrypt) password hashing (memory-hard, highly secure)
- Input validation and sanitization
- Token expiration and refresh mechanism
- Secure password requirements
- Timing-safe password comparison to prevent timing attacks
✅ Easy Integration
- Plug-and-play API
- Express middleware for protected routes
- Request data validation middleware with custom messages
- Role-based access control
- Automatic route setup
- TypeScript support
✅ Flexible
- Works with any database/user repository
- Configurable token expiration
- Optional refresh tokens
- Customizable user fields
✅ End-to-End Encryption (NEW in v1.3.0)
- AES-256-GCM authenticated encryption
- Automatic chunking for large data
- Password-based key derivation (PBKDF2)
- Secure IV/nonce generation
- Support for strings and Buffers
- Easy-to-use API for encryption/decryption
Peer Dependencies
Make sure you have the following peer dependencies installed:
npm install expressQuick Start
1. Install Dependencies
npm install @bhandari88/express-auth express
npm install --save-dev typescript @types/express @types/node2. Create User Repository
Implement the UserRepository interface for your database:
import { UserRepository, UserDocument } from '@bhandari88/express-auth';
class MyUserRepository implements UserRepository {
async findById(id: string): Promise<UserDocument | null> {
// Your database query logic
}
async findByEmail(email: string): Promise<UserDocument | null> {
// Your database query logic
}
async findByUsername(username: string): Promise<UserDocument | null> {
// Your database query logic
}
async findByPhone(phone: string): Promise<UserDocument | null> {
// Your database query logic
}
async findByProvider(provider: string, providerId: string): Promise<UserDocument | null> {
// Your database query logic
}
async create(userData: Partial<UserDocument>): Promise<UserDocument> {
// Your database create logic
}
async update(id: string, updateData: Partial<UserDocument>): Promise<UserDocument | null> {
// Your database update logic
}
async delete(id: string): Promise<boolean> {
// Your database delete logic
}
}3. Configure and Initialize Auth
import express from 'express';
import { Auth, AuthConfig } from '@bhandari88/express-auth';
import MyUserRepository from './repositories/MyUserRepository';
const app = express();
app.use(express.json());
const authConfig: AuthConfig = {
jwtSecret: process.env.JWT_SECRET || 'your-secret-key',
jwtExpiresIn: '15m',
refreshTokenSecret: process.env.REFRESH_TOKEN_SECRET || 'your-refresh-secret',
refreshTokenExpiresIn: '7d',
bcryptRounds: 16384, // Scrypt cost parameter (2^14, configurable)
enableRefreshTokens: true,
socialAuth: {
google: {
clientID: process.env.GOOGLE_CLIENT_ID || '',
clientSecret: process.env.GOOGLE_CLIENT_SECRET || '',
callbackURL: 'http://localhost:3000/api/auth/google/callback',
},
facebook: {
clientID: process.env.FACEBOOK_CLIENT_ID || '',
clientSecret: process.env.FACEBOOK_CLIENT_SECRET || '',
callbackURL: 'http://localhost:3000/api/auth/facebook/callback',
},
},
};
const userRepository = new MyUserRepository();
const auth = new Auth(authConfig, userRepository);
// Setup all auth routes automatically
auth.setupRoutes(app, '/api/auth');
app.listen(3000, () => {
console.log('Server running on port 3000');
});4. Use Protected Routes
// Protect a route
app.get('/api/profile', auth.getAuthMiddleware(), (req, res) => {
const user = (req as any).user;
res.json({ user });
});
// Optional authentication (doesn't fail if no token)
app.get('/api/public', auth.getOptionalAuthMiddleware(), (req, res) => {
const user = (req as any).user; // May be undefined
res.json({ user });
});
// Role-based access control
app.get('/api/admin',
auth.getAuthMiddleware(),
auth.requireRole(['admin', 'superadmin']),
(req, res) => {
res.json({ message: 'Admin access granted' });
}
);5. Use Validation Middleware
The package includes a powerful validation middleware for request data validation with custom error messages. No third-party dependencies required!
Basic Usage
import { validate, validateBody, validateQuery, validateParams } from '@bhandari88/express-auth';
// Validate request body
app.post('/api/users', validateBody({
email: {
rules: [
{ type: 'required', message: 'Email is required' },
{ type: 'email', message: 'Invalid email format' }
]
},
password: {
rules: [
{ type: 'required', message: 'Password is required' },
{ type: 'minLength', value: 8, message: 'Password must be at least 8 characters' }
]
},
age: {
rules: [
{ type: 'number', message: 'Age must be a number' },
{ type: 'min', value: 18, message: 'Age must be at least 18' },
{ type: 'max', value: 120, message: 'Age must be at most 120' }
]
}
}), (req, res) => {
// req.body is validated
res.json({ message: 'User created', data: req.body });
});Shorthand Syntax
You can also use a simpler array syntax:
app.post('/api/register', validateBody({
username: [
{ type: 'required', message: 'Username is required' },
{ type: 'minLength', value: 3, message: 'Username must be at least 3 characters' },
{ type: 'maxLength', value: 30, message: 'Username must be at most 30 characters' },
{ type: 'pattern', value: /^[a-zA-Z0-9_-]+$/, message: 'Username can only contain letters, numbers, underscores, and hyphens' }
],
email: [
{ type: 'required' },
{ type: 'email', message: 'Invalid email address' }
]
}), handler);Validate Query Parameters
app.get('/api/users', validateQuery({
page: [
{ type: 'number', message: 'Page must be a number' },
{ type: 'min', value: 1, message: 'Page must be at least 1' }
],
limit: [
{ type: 'number' },
{ type: 'min', value: 1 },
{ type: 'max', value: 100, message: 'Limit cannot exceed 100' }
],
status: [
{ type: 'enum', values: ['active', 'inactive', 'pending'], message: 'Status must be active, inactive, or pending' }
]
}), (req, res) => {
// req.query is validated
res.json({ users: [] });
});Validate Route Parameters
app.get('/api/users/:id', validateParams({
id: [
{ type: 'required', message: 'User ID is required' },
{ type: 'pattern', value: /^[a-f\d]{24}$/i, message: 'Invalid user ID format' }
]
}), (req, res) => {
// req.params is validated
res.json({ user: {} });
});Validate Multiple Sources
app.put('/api/users/:id', validate({
id: [
{ type: 'required' },
{ type: 'pattern', value: /^[a-f\d]{24}$/i }
],
email: [
{ type: 'email' }
],
status: [
{ type: 'enum', values: ['active', 'inactive'] }
]
}, {
source: ['params', 'body'] // Validate both params and body
}), handler);Custom Validation
app.post('/api/custom', validateBody({
password: [
{ type: 'required' },
{ type: 'minLength', value: 8 },
{
type: 'custom',
validator: (value) => {
// Custom validation logic
return /[A-Z]/.test(value) && /[a-z]/.test(value) && /[0-9]/.test(value);
},
message: 'Password must contain uppercase, lowercase, and number'
}
],
confirmPassword: [
{ type: 'required' },
{
type: 'custom',
validator: (value, data) => {
// Access other fields from the request
return value === data.password;
},
message: 'Passwords do not match'
}
]
}), handler);Available Validation Rules
| Rule | Description | Example |
|------|-------------|---------|
| required | Field must be present and not empty | { type: 'required', message: 'Field is required' } |
| string | Value must be a string | { type: 'string' } |
| number | Value must be a number | { type: 'number' } |
| boolean | Value must be a boolean | { type: 'boolean' } |
| email | Value must be a valid email | { type: 'email', message: 'Invalid email' } |
| min | Number must be >= value | { type: 'min', value: 1 } |
| max | Number must be <= value | { type: 'max', value: 100 } |
| minLength | String length must be >= value | { type: 'minLength', value: 8 } |
| maxLength | String length must be <= value | { type: 'maxLength', value: 255 } |
| pattern | String must match regex | { type: 'pattern', value: /^[a-z]+$/ } |
| enum | Value must be in array | { type: 'enum', values: ['a', 'b', 'c'] } |
| array | Value must be an array | { type: 'array' } |
| object | Value must be an object | { type: 'object' } |
| custom | Custom validation function | { type: 'custom', validator: (val) => boolean } |
Validation Options
validate(schema, {
source: 'body' | 'query' | 'params' | ['body', 'query'], // What to validate (default: 'body')
abortEarly: boolean // Stop on first error or collect all errors (default: false)
})Error Response Format
When validation fails, the middleware returns a 400 status with:
{
"success": false,
"error": "Validation failed",
"errors": {
"email": "Invalid email format",
"password": "Password must be at least 8 characters"
}
}Or with abortEarly: true, only the first error:
{
"success": false,
"error": "Validation failed",
"errors": {
"email": "Email is required"
}
}6. End-to-End Data Encryption & Decryption
The package includes a powerful encryption service for secure end-to-end encryption and decryption of large data using AES-256-GCM with automatic chunking support.
Basic Usage
import { EncryptionService } from '@bhandari88/express-auth';
// Create encryption service with default settings
const encryption = new EncryptionService();
// Encrypt data
const data = 'Sensitive information that needs to be encrypted';
const password = 'my-secure-password-123';
const encrypted = await encryption.encrypt(data, password);
console.log(encrypted);
// {
// encrypted: 'base64-encrypted-data...',
// salt: 'base64-salt...',
// iv: 'base64-iv...',
// authTag: 'base64-auth-tag...',
// algorithm: 'aes-256-gcm'
// }
// Decrypt data
const decrypted = await encryption.decrypt(encrypted, password);
console.log(decrypted.toString('utf8')); // 'Sensitive information that needs to be encrypted'Encrypt/Decrypt Strings (Convenience Methods)
// Encrypt to string (includes all metadata as JSON)
const encryptedString = await encryption.encryptToString(data, password);
// Store encryptedString in database, file, etc.
// Decrypt from string
const decryptedString = await encryption.decryptFromString(encryptedString, password);
console.log(decryptedString); // Original dataEncrypt/Decrypt Large Data
The encryption service automatically handles large data by chunking it into smaller pieces. This is transparent to you:
// Encrypt large file or data
const fs = require('fs');
const largeData = fs.readFileSync('large-file.pdf');
const encrypted = await encryption.encrypt(largeData, password);
// Automatically chunked and encrypted
// Decrypt large data
const decrypted = await encryption.decrypt(encrypted, password);
fs.writeFileSync('decrypted-file.pdf', decrypted);Encrypt/Decrypt Buffers
// Encrypt Buffer
const buffer = Buffer.from('Binary data', 'utf8');
const encrypted = await encryption.encrypt(buffer, password);
// Decrypt to Buffer
const decrypted = await encryption.decrypt(encrypted, password);
console.log(decrypted); // BufferCustom Configuration
import { EncryptionService, EncryptionConfig } from '@bhandari88/express-auth';
const config: EncryptionConfig = {
algorithm: 'aes-256-gcm', // 'aes-256-gcm' | 'aes-192-gcm' | 'aes-128-gcm'
keyDerivationIterations: 100000, // PBKDF2 iterations (higher = more secure but slower)
saltLength: 32, // Salt length in bytes
ivLength: 16, // IV length in bytes
authTagLength: 16, // Auth tag length in bytes
chunkSize: 64 * 1024, // Chunk size for large data (64KB default)
};
const encryption = new EncryptionService(config);Generate Secure Passwords
import { EncryptionService } from '@bhandari88/express-auth';
// Generate secure random password (base64)
const password = EncryptionService.generateSecurePassword(32);
console.log(password); // Random base64 string
// Generate secure random password (hex)
const hexPassword = EncryptionService.generateSecurePasswordHex(32);
console.log(hexPassword); // Random hex stringExpress Route Example
import express from 'express';
import { EncryptionService } from '@bhandari88/express-auth';
const app = express();
app.use(express.json());
const encryption = new EncryptionService();
// Encrypt endpoint
app.post('/api/encrypt', async (req, res) => {
try {
const { data, password } = req.body;
if (!data || !password) {
return res.status(400).json({
success: false,
error: 'Data and password are required'
});
}
const encrypted = await encryption.encryptToString(data, password);
res.json({ success: true, encrypted });
} catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Encryption failed'
});
}
});
// Decrypt endpoint
app.post('/api/decrypt', async (req, res) => {
try {
const { encrypted, password } = req.body;
if (!encrypted || !password) {
return res.status(400).json({
success: false,
error: 'Encrypted data and password are required'
});
}
const decrypted = await encryption.decryptFromString(encrypted, password);
res.json({ success: true, decrypted });
} catch (error) {
res.status(500).json({
success: false,
error: error instanceof Error ? error.message : 'Decryption failed'
});
}
});Security Features
- AES-256-GCM: Industry-standard authenticated encryption
- Automatic Chunking: Handles large data efficiently without memory issues
- PBKDF2 Key Derivation: 100,000 iterations by default (configurable)
- Random Salt & IV: Unique salt and IV for each encryption
- Authentication Tags: Prevents tampering with encrypted data
- No External Dependencies: Uses Node.js built-in crypto module
Encryption Configuration Options
interface EncryptionConfig {
algorithm?: 'aes-256-gcm' | 'aes-192-gcm' | 'aes-128-gcm'; // Default: 'aes-256-gcm'
keyDerivationIterations?: number; // Default: 100000
saltLength?: number; // Default: 32 bytes
ivLength?: number; // Default: 16 bytes
authTagLength?: number; // Default: 16 bytes
chunkSize?: number; // Default: 64KB
}Encryption Result Format
interface EncryptionResult {
encrypted: string; // Base64 encoded encrypted data
salt: string; // Base64 encoded salt used for key derivation
iv: string; // Base64 encoded IV/nonce
authTag?: string; // Base64 encoded authentication tag (for single chunk)
algorithm: string; // Algorithm used ('aes-256-gcm')
}API Endpoints
Register User
POST /api/auth/register
{
"email": "[email protected]",
"password": "securepassword123"
}Or with username:
{
"username": "johndoe",
"password": "securepassword123"
}Or with phone:
{
"phone": "+1234567890",
"password": "securepassword123"
}Response:
{
"success": true,
"user": {
"id": "user-id",
"email": "[email protected]",
"verified": false
},
"tokens": {
"accessToken": "jwt-token",
"refreshToken": "refresh-token",
"expiresIn": 900
},
"message": "User registered successfully"
}Login
POST /api/auth/login
{
"email": "[email protected]",
"password": "securepassword123"
}Or with username:
{
"username": "johndoe",
"password": "securepassword123"
}Or with phone:
{
"phone": "+1234567890",
"password": "securepassword123"
}Response:
{
"success": true,
"user": {
"id": "user-id",
"email": "[email protected]"
},
"tokens": {
"accessToken": "jwt-token",
"refreshToken": "refresh-token",
"expiresIn": 900
},
"message": "Login successful"
}Refresh Token
POST /api/auth/refresh
{
"refreshToken": "your-refresh-token"
}Response:
{
"success": true,
"tokens": {
"accessToken": "new-jwt-token",
"refreshToken": "new-refresh-token",
"expiresIn": 900
},
"message": "Token refreshed successfully"
}Change Password
POST /api/auth/change-password
Headers:
Authorization: Bearer <access-token>Body:
{
"oldPassword": "oldpassword123",
"newPassword": "newpassword456"
}Social Login
Google:
- GET
/api/auth/google- Initiate Google login - GET
/api/auth/google/callback- Google callback (configured automatically)
Facebook:
- GET
/api/auth/facebook- Initiate Facebook login - GET
/api/auth/facebook/callback- Facebook callback (configured automatically)
Programmatic Usage
You can also use the auth methods programmatically:
// Register
const result = await auth.register({
email: '[email protected]',
password: 'password123',
});
// Login
const loginResult = await auth.login({
email: '[email protected]',
password: 'password123',
});
// Change password
const changeResult = await auth.changePassword(
userId,
'oldPassword',
'newPassword'
);
// Refresh token
const refreshResult = await auth.refreshToken(refreshToken);Configuration Options
interface AuthConfig {
jwtSecret: string; // Required: Secret for JWT signing
jwtExpiresIn?: string; // Optional: Access token expiration (default: '15m')
refreshTokenSecret?: string; // Optional: Secret for refresh tokens (defaults to jwtSecret)
refreshTokenExpiresIn?: string; // Optional: Refresh token expiration (default: '7d')
bcryptRounds?: number; // Optional: Scrypt cost parameter N (default: 16384 = 2^14)
enableRefreshTokens?: boolean; // Optional: Enable refresh tokens (default: true)
socialAuth?: { // Optional: Social auth configuration
google?: {
clientID: string;
clientSecret: string;
callbackURL: string;
};
facebook?: {
clientID: string;
clientSecret: string;
callbackURL: string;
};
};
}Security Best Practices
Environment Variables: Always use environment variables for secrets:
JWT_SECRET=your-super-secret-key REFRESH_TOKEN_SECRET=your-refresh-secret-key GOOGLE_CLIENT_ID=your-google-client-id GOOGLE_CLIENT_SECRET=your-google-client-secretPassword Requirements:
- Minimum 6 characters
- Maximum 128 characters
- Hashed with Node.js crypto.scrypt (memory-hard, highly secure)
- Uses timing-safe comparison to prevent timing attacks
- Default cost parameter: 16384 (2^14)
Token Expiration:
- Access tokens: Short-lived (15 minutes default)
- Refresh tokens: Long-lived (7 days default)
HTTPS: Always use HTTPS in production
Input Validation: All inputs are validated and sanitized automatically
Data Encryption:
- Use AES-256-GCM for authenticated encryption
- Minimum 8 character password for encryption
- Store encrypted data securely (database, files, etc.)
- Never share encryption passwords in plain text
- Use strong, randomly generated passwords when possible
Types
interface User {
id: string;
email?: string;
username?: string;
phone?: string;
password?: string;
provider?: 'local' | 'google' | 'facebook';
providerId?: string;
verified?: boolean;
[key: string]: any; // Additional custom fields
}
interface AuthResult {
success: boolean;
user?: User;
tokens?: AuthTokens;
message?: string;
error?: string;
}
interface AuthTokens {
accessToken: string;
refreshToken?: string;
expiresIn: number;
}Example with MongoDB (Mongoose)
See example/usage.ts for a complete example using MongoDB with Mongoose.
License
MIT
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
