authcontroller
v1.0.1
Published
Zero-boilerplate authentication package for Node.js - JWT, Sessions, Email Verification, Role-based Access Control
Maintainers
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 authcontrollerPeer Dependencies
npm install expressOptional 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 # ResendQuick 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 manuallyReset 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
Use Environment Variables
AUTH_SECRET=your-256-bit-secret-keyEnable HTTPS in Production
session: { secure: process.env.NODE_ENV === 'production', }Set Appropriate Token Expiry
jwt: { accessTokenExpiry: '15m', // Short-lived refreshTokenExpiry: '7d', // Longer, but not indefinite }Require Email Verification
emailVerification: { enabled: true, requireVerification: true, // Block login until verified }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.
- Fork the repository
- Create your feature branch (
git checkout -b feature/amazing-feature) - Commit your changes (
git commit -m 'Add amazing feature') - Push to the branch (
git push origin feature/amazing-feature) - Open a Pull Request
