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 🙏

© 2025 – Pkg Stats / Ryan Hefner

authcontroller

v1.0.1

Published

Zero-boilerplate authentication package for Node.js - JWT, Sessions, Email Verification, Role-based Access Control

Readme


Overview

AuthController is a comprehensive, opinionated authentication package that eliminates boilerplate code. Get production-ready auth in minutes with a single function call.

const auth = createAuth({
  secret: process.env.AUTH_SECRET,
  database: { type: 'mongoose', userModel: User },
});

app.use('/auth', auth.router); // All routes ready!

Features

| Feature | Description | |---------|-------------| | Dual Auth Modes | JWT tokens, cookie sessions, or both | | Role-Based Access | Built-in admin/user/moderator roles | | Email Verification | Complete verification flow with tokens | | Password Reset | Secure forgot/reset password system | | Session Management | View and revoke active sessions | | Database Adapters | First-class Mongoose and Prisma support | | Email Providers | Nodemailer, SendGrid, Resend integration | | Pre-built Router | Optional Express router with all endpoints |


Installation

npm install authcontroller

Peer Dependencies

npm install express

Optional Dependencies

Install based on your stack:

# Database (choose one)
npm install mongoose        # For MongoDB
npm install @prisma/client  # For Prisma

# Email (choose one or more)
npm install nodemailer      # SMTP email
npm install @sendgrid/mail  # SendGrid
npm install resend          # Resend

Quick Start

1. Define Your User Model

Mongoose:

import mongoose from 'mongoose';

const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  name: String,
  _auth: {
    password: { type: String, required: true },
    role: { type: String, default: 'user' },
    emailVerified: { type: Boolean, default: false },
    verificationToken: {
      tokenHash: String,
      expiresAt: Date,
    },
    passwordResetToken: {
      tokenHash: String,
      expiresAt: Date,
    },
  },
}, { timestamps: true });

export const User = mongoose.model('User', userSchema);

Prisma:

model User {
  id        String   @id @default(cuid())
  email     String   @unique
  name      String?
  auth      Json     @default("{}")
  createdAt DateTime @default(now())
  updatedAt DateTime @updatedAt
}

model Session {
  id             String   @id @default(cuid())
  userId         String
  expiresAt      DateTime
  userAgent      String?
  ipAddress      String?
  createdAt      DateTime @default(now())
  lastAccessedAt DateTime @default(now())
}

2. Initialize AuthController

import express from 'express';
import cookieParser from 'cookie-parser';
import { createAuth } from 'authcontroller';
import { User } from './models/User';

const app = express();
app.use(express.json());
app.use(cookieParser());

const auth = createAuth({
  secret: process.env.AUTH_SECRET!,
  database: {
    type: 'mongoose',
    userModel: User,
  },
});

// Mount the pre-built router
app.use('/auth', auth.router);

// Protect routes
app.get('/profile', auth.middleware, (req, res) => {
  res.json({ user: req.user });
});

app.get('/admin', auth.middleware, auth.requireRole('admin'), (req, res) => {
  res.json({ message: 'Welcome, Admin!' });
});

3. Available Endpoints

When using auth.router, these endpoints are automatically available:

| Method | Endpoint | Description | |--------|----------|-------------| | POST | /auth/register | Register new user | | POST | /auth/login | Login with credentials | | POST | /auth/logout | Logout (revoke token) | | POST | /auth/refresh-token | Refresh access token | | POST | /auth/forgot-password | Request password reset | | POST | /auth/reset-password | Reset password with token | | GET | /auth/verify-email | Verify email with token | | POST | /auth/resend-verification | Resend verification email |


Configuration

Full Configuration Options

const auth = createAuth({
  // Required
  secret: string,

  // Database Configuration
  database: {
    type: 'mongoose' | 'prisma',
    userModel: Model,              // Your user model
    sessionModel?: Model,          // Optional: custom session model
    prismaClient?: PrismaClient,   // Required for Prisma
    authField?: string,            // Default: '_auth' (Mongoose) or 'auth' (Prisma)
  },

  // Authentication Mode
  mode: 'jwt' | 'session' | 'both',  // Default: 'jwt'

  // JWT Configuration
  jwt?: {
    accessTokenExpiry: string,     // Default: '15m'
    refreshTokenExpiry: string,    // Default: '7d'
  },

  // Session Configuration
  session?: {
    cookieName: string,            // Default: 'authprovider_session'
    maxAge: number,                // Default: 7 days (ms)
    secure: boolean,               // Default: true in production
    httpOnly: boolean,             // Default: true
    sameSite: 'strict' | 'lax' | 'none',  // Default: 'lax'
  },

  // Role Configuration
  roles?: {
    default: string,               // Default: 'user'
    available: string[],           // Default: ['user', 'admin', 'moderator']
  },

  // Email Provider
  email?: {
    provider: 'nodemailer' | 'sendgrid' | 'resend',
    from: string,                  // Sender email address
    config: ProviderConfig,        // Provider-specific config
  },

  // Email Verification
  emailVerification?: {
    enabled: boolean,              // Default: false
    tokenExpiry: string,           // Default: '24h'
    requireVerification: boolean,  // Default: false (block login if not verified)
  },

  // Password Reset
  passwordReset?: {
    tokenExpiry: string,           // Default: '1h'
  },

  // Application URL (for email links)
  baseUrl?: string,
});

Email Provider Configuration

Nodemailer (SMTP):

email: {
  provider: 'nodemailer',
  from: '[email protected]',
  config: {
    host: 'smtp.gmail.com',
    port: 587,
    secure: false,
    auth: {
      user: process.env.SMTP_USER,
      pass: process.env.SMTP_PASS,
    },
  },
}

SendGrid:

email: {
  provider: 'sendgrid',
  from: '[email protected]',
  config: {
    apiKey: process.env.SENDGRID_API_KEY,
  },
}

Resend:

email: {
  provider: 'resend',
  from: '[email protected]',
  config: {
    apiKey: process.env.RESEND_API_KEY,
  },
}

API Reference

Middleware

auth.middleware

Requires authentication. Validates JWT or session and populates req.user.

app.get('/protected', auth.middleware, (req, res) => {
  // req.user = { userId: string, role: string }
});

auth.optionalAuth

Optional authentication. Populates req.user if token provided, continues without error if not.

app.get('/public', auth.optionalAuth, (req, res) => {
  if (req.user) {
    // Authenticated user
  } else {
    // Guest user
  }
});

auth.requireRole(roles)

Requires specific role(s). Must be used after auth.middleware.

// Single role
app.get('/admin', auth.middleware, auth.requireRole('admin'), handler);

// Multiple roles (OR logic)
app.get('/moderate', auth.middleware, auth.requireRole(['admin', 'moderator']), handler);

Services

Registration

const { user, accessToken, refreshToken, session } = await auth.services.register(
  { email: '[email protected]', name: 'John Doe' },
  'password123',
  'user'  // optional role
);

Login

const { user, accessToken, refreshToken, session } = await auth.services.login(
  '[email protected]',
  'password123'
);

Logout

// Logout single session
await auth.services.logout(accessToken);

// Logout all sessions for user
await auth.services.logoutAll(userId);

Token Refresh

const { accessToken, refreshToken } = await auth.services.refreshToken(oldRefreshToken);

Password Services

Request Password Reset

const { token, emailSent } = await auth.services.password.createResetToken('[email protected]');

// If email provider configured, email is sent automatically
// Otherwise, use the returned token to send manually

Reset Password

await auth.services.password.resetPassword(resetToken, 'newPassword123');

Change Password (Authenticated)

await auth.services.password.changePassword(
  userId,
  'currentPassword',
  'newPassword123'
);

Email Verification Services

Send Verification Email

await auth.services.verification.sendVerificationEmail(userId);

Verify Email

const { success, user } = await auth.services.verification.verifyEmail(token);

Resend Verification

await auth.services.verification.resendVerificationEmail('[email protected]');

Check Verification Status

const isVerified = await auth.services.verification.isEmailVerified(userId);

Session Services

Get Active Sessions

const sessions = await auth.services.sessions.getActiveSessions(userId);

// Returns array of:
// {
//   id: string,
//   userId: string,
//   createdAt: Date,
//   lastAccessedAt: Date,
//   userAgent?: string,
//   ipAddress?: string,
// }

Revoke Session

await auth.services.sessions.revokeSession(sessionId);

Revoke All Sessions

await auth.services.sessions.revokeAllSessions(userId);

User Services

Find User

const user = await auth.services.users.findById(userId);
const user = await auth.services.users.findByEmail('[email protected]');

Update Role

await auth.services.users.updateRole(userId, 'admin');

Utility Functions

// Password hashing
const hash = await auth.utils.hashPassword('password123');
const isValid = await auth.utils.comparePassword('password123', hash);

// JWT tokens
const token = auth.utils.generateToken({ userId, role });
const payload = auth.utils.verifyToken(token);

// Session cookies
auth.utils.setSessionCookie(res, sessionId);
auth.utils.clearSessionCookie(res);

Examples

Complete Express Server

import express from 'express';
import mongoose from 'mongoose';
import cookieParser from 'cookie-parser';
import { createAuth } from 'authcontroller';

// User Model
const userSchema = new mongoose.Schema({
  email: { type: String, required: true, unique: true },
  name: String,
  _auth: {
    password: { type: String, required: true },
    role: { type: String, default: 'user' },
    emailVerified: { type: Boolean, default: false },
    verificationToken: { tokenHash: String, expiresAt: Date },
    passwordResetToken: { tokenHash: String, expiresAt: Date },
  },
}, { timestamps: true });

const User = mongoose.model('User', userSchema);

// Initialize Auth
const auth = createAuth({
  secret: process.env.AUTH_SECRET!,
  database: { type: 'mongoose', userModel: User },
  mode: 'both',
  roles: {
    default: 'user',
    available: ['user', 'admin', 'moderator'],
  },
  email: {
    provider: 'nodemailer',
    from: '[email protected]',
    config: {
      host: 'smtp.gmail.com',
      port: 587,
      auth: {
        user: process.env.SMTP_USER,
        pass: process.env.SMTP_PASS,
      },
    },
  },
  emailVerification: {
    enabled: true,
    requireVerification: false,
  },
  baseUrl: 'http://localhost:3000',
});

// Express App
const app = express();
app.use(express.json());
app.use(cookieParser());

// Pre-built auth routes
app.use('/auth', auth.router);

// Public route
app.get('/', (req, res) => {
  res.json({ message: 'Welcome to the API' });
});

// Protected route
app.get('/me', auth.middleware, (req, res) => {
  res.json({ user: req.user });
});

// Admin route
app.get('/admin', auth.middleware, auth.requireRole('admin'), (req, res) => {
  res.json({ message: 'Admin Dashboard' });
});

// Optional auth route
app.get('/public', auth.optionalAuth, (req, res) => {
  res.json({
    message: req.user ? `Hello, ${req.user.userId}!` : 'Hello, Guest!',
  });
});

// Custom: Change password
app.post('/change-password', auth.middleware, async (req, res) => {
  try {
    await auth.services.password.changePassword(
      req.user!.userId,
      req.body.currentPassword,
      req.body.newPassword
    );
    res.json({ success: true });
  } catch (error: any) {
    res.status(400).json({ error: error.message });
  }
});

// Custom: Get sessions
app.get('/sessions', auth.middleware, async (req, res) => {
  const sessions = await auth.services.sessions.getActiveSessions(req.user!.userId);
  res.json({ sessions });
});

// Start server
mongoose.connect(process.env.MONGO_URI!).then(() => {
  app.listen(3000, () => console.log('Server running on port 3000'));
});

Custom Routes (Without Pre-built Router)

const auth = createAuth({
  secret: process.env.AUTH_SECRET!,
  database: { type: 'mongoose', userModel: User },
});

// Custom register
app.post('/signup', async (req, res) => {
  try {
    const result = await auth.services.register(
      { email: req.body.email, name: req.body.name },
      req.body.password
    );
    res.status(201).json(result);
  } catch (error: any) {
    res.status(400).json({ error: error.message });
  }
});

// Custom login
app.post('/signin', async (req, res) => {
  try {
    const result = await auth.services.login(req.body.email, req.body.password);
    res.json(result);
  } catch (error: any) {
    res.status(401).json({ error: error.message });
  }
});

// Custom logout
app.post('/signout', auth.middleware, async (req, res) => {
  const token = req.headers.authorization?.split(' ')[1];
  if (token) {
    await auth.services.logout(token);
  }
  res.json({ success: true });
});

TypeScript Support

AuthController is written in TypeScript and provides full type definitions.

Request Type Extension

declare global {
  namespace Express {
    interface Request {
      user?: {
        userId: string;
        role: string;
      };
      sessionId?: string;
    }
  }
}

Importing Types

import type {
  AuthControllerConfig,
  AuthController,
  User,
  AuthUser,
  Session,
  JwtPayload,
  RegisterResult,
  LoginResult,
} from 'authcontroller';

Error Handling

AuthController throws specific error types for different scenarios:

| Error | Status | Description | |-------|--------|-------------| | InvalidCredentialsError | 401 | Invalid email or password | | UserNotFoundError | 404 | User does not exist | | UserExistsError | 409 | Email already registered | | TokenExpiredError | 401 | JWT or reset token expired | | InvalidTokenError | 401 | Malformed or invalid token | | UnauthorizedError | 401 | Authentication required | | ForbiddenError | 403 | Insufficient permissions | | EmailNotVerifiedError | 403 | Email verification required | | ValidationError | 400 | Invalid input data |

Error Handler Example

app.use((err, req, res, next) => {
  if (err.name === 'InvalidCredentialsError') {
    return res.status(401).json({ error: 'Invalid email or password' });
  }
  if (err.name === 'ForbiddenError') {
    return res.status(403).json({ error: 'Access denied' });
  }
  res.status(500).json({ error: 'Internal server error' });
});

Security Best Practices

  1. Use Environment Variables

    AUTH_SECRET=your-256-bit-secret-key
  2. Enable HTTPS in Production

    session: {
      secure: process.env.NODE_ENV === 'production',
    }
  3. Set Appropriate Token Expiry

    jwt: {
      accessTokenExpiry: '15m',   // Short-lived
      refreshTokenExpiry: '7d',   // Longer, but not indefinite
    }
  4. Require Email Verification

    emailVerification: {
      enabled: true,
      requireVerification: true,  // Block login until verified
    }
  5. Use Strong Password Validation

    • Minimum 8 characters (enforced by default)
    • Add custom validation as needed

License

MIT License - see LICENSE file for details.


Contributing

Contributions are welcome! Please feel free to submit a Pull Request.

  1. Fork the repository
  2. Create your feature branch (git checkout -b feature/amazing-feature)
  3. Commit your changes (git commit -m 'Add amazing feature')
  4. Push to the branch (git push origin feature/amazing-feature)
  5. Open a Pull Request