npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@bhandari88/express-auth

v1.2.0

Published

Plug-and-play authentication handler for Express.js with TypeScript supporting email, username, phone, and social login

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-auth

Package 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

Peer Dependencies

Make sure you have the following peer dependencies installed:

npm install express

Quick Start

1. Install Dependencies

npm install @bhandari88/express-auth express
npm install --save-dev typescript @types/express @types/node

2. 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"
  }
}

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

  1. 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-secret
  2. Password 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)
  3. Token Expiration:

    • Access tokens: Short-lived (15 minutes default)
    • Refresh tokens: Long-lived (7 days default)
  4. HTTPS: Always use HTTPS in production

  5. Input Validation: All inputs are validated and sanitized automatically

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.