authstack
v1.0.11
Published
Production-ready authentication library for Node.js and Express
Maintainers
Readme
- Signup validation → hours of work
- Password hashing → easy to get wrong
- JWT + refresh tokens → tricky to implement securely
- Refresh token rotation → most devs skip this
- OTP via email → another integration
- Forgot password flow → more boilerplate
- 2FA with QR codes → complex setup
- Passkeys (WebAuthn) → days of work
- Brute force protection → often forgotten
- Session tracking → even more codeEvery project. Every time. Same code.
npm install authstackconst auth = new AuthStack({ userModel: User, ...config });
app.post("/signup", auth.signup()); // ✅ validation, hashing, duplicate check
app.post("/signin", auth.signin()); // ✅ brute force, lockout, 2FA check
app.post("/signout", auth.signout()); // ✅ cookie clear, session remove
// + 13 more routes. All secure. All production-ready.You bring your models. AuthStack brings the rest.
📦 What's Inside
🔑 Core Auth
| Route | What it does |
| --------------------- | ------------------------------- |
| POST /signup | Validate + hash + create user |
| POST /signin | Secure login + session tracking |
| POST /signout | Clear cookies + remove session |
| POST /refresh-token | Rotate tokens securely |
📧 Email & OTP
| Route | What it does |
| ----------------------- | ---------------------------- |
| POST /send-otp | 6-digit OTP via your mail fn |
| POST /verify-email | Verify OTP → mark verified |
| POST /forget-password | OTP for password reset |
| POST /verify-otp | Verify reset OTP |
| POST /reset-password | Set new password |
🔐 Two-Factor Auth
| Route | What it does |
| -------------------- | ------------------------ |
| POST /2fa/generate | TOTP secret + QR code |
| POST /2fa/enable | Verify code + enable 2FA |
| POST /2fa/login | Complete 2FA login |
🪪 Passkeys (WebAuthn)
| Route | What it does |
| ------------------------------- | --------------------------- |
| POST /passkey/register/start | Start registration ceremony |
| POST /passkey/register/verify | Verify + store credential |
| POST /passkey/login/start | Start login ceremony |
| POST /passkey/login/verify | Verify + issue tokens |
🚀 Quick Start
1 — Install
npm install authstack2 — Initialize
// auth.config.js
import { AuthStack } from "authstack";
import User from "./models/User.js";
import Session from "./models/Session.js";
import redis from "./config/redis.js";
import { sendMail } from "./utils/sendMail.js";
const auth = new AuthStack({
userModel: User, // your Mongoose User model
sessionModel: Session, // your Session model
redis: redis, // your Redis client
jwtSecret: process.env.JWT_SECRET,
allowedRoles: ["user", "admin"],
sendMail: sendMail, // your mail function
// Only needed for Passkeys ↓
rpName: "MyApp",
rpID: "myapp.com",
origin: "https://myapp.com",
});
export default auth;3 — Mount Routes
// routes/auth.routes.js
import express from "express";
import auth from "../auth.config.js";
const router = express.Router();
// ── Core ──────────────────────────────────────────────────────
router.post("/signup", auth.signup());
router.post("/signin", auth.signin());
router.post("/signout", auth.signout());
router.post("/refresh-token", auth.refreshTokenRotation());
// ── Email Verification ────────────────────────────────────────
router.post("/send-otp", auth.sendOtpEmail());
router.post("/verify-email", auth.verifyEmailOtp());
// ── Password Reset ────────────────────────────────────────────
router.post("/forget-password", auth.forgotPassword());
router.post("/verify-otp", auth.verifyOtp());
router.post("/reset-password", auth.resetPassword());
// ── Two-Factor Auth ───────────────────────────────────────────
router.post("/2fa/generate", auth.generate2FASecret());
router.post("/2fa/enable", auth.verifyAndEnable2FA());
router.post("/2fa/login", auth.verify2FALogin());
// ── Passkeys (WebAuthn) ───────────────────────────────────────
router.post("/passkey/register/start", auth.startPasskeyRegistration());
router.post("/passkey/register/verify", auth.verifyPasskeyRegistration());
router.post("/passkey/login/start", auth.startPasskeyLogin());
router.post("/passkey/login/verify", auth.verifyPasskeyLogin());
export default router;// app.js
import express from "express";
import authRoutes from "./routes/auth.routes.js";
const app = express();
app.use(express.json());
app.use("/auth", authRoutes);
app.listen(3000, () => console.log("🚀 Server ready"));Done. 16 auth routes. Zero boilerplate.
⚙️ Configuration
| Option | Type | Required | Default | Description |
| -------------- | -------------- | -------- | ---------- | ------------------------------- |
| userModel | Mongoose Model | ✅ | — | Your User model |
| sessionModel | Mongoose Model | ✅ | — | Your Session model |
| redis | Redis Client | ✅ | — | ioredis or node-redis |
| jwtSecret | string | ✅ | — | JWT signing secret |
| allowedRoles | string[] | ❌ | ['user'] | Valid roles on signup |
| sendMail | async fn | ✅ | — | ({ to, subject, text, html }) |
| rpName | string | Passkeys | — | App name for WebAuthn |
| rpID | string | Passkeys | — | Domain e.g. myapp.com |
| origin | string | Passkeys | — | Origin e.g. https://myapp.com |
sendMail — works with any provider
// Nodemailer
export const sendMail = async ({ to, subject, text, html }) => {
await transporter.sendMail({
from: "[email protected]",
to,
subject,
text,
html,
});
};
// Resend
export const sendMail = async ({ to, subject, html }) => {
await resend.emails.send({ from: "[email protected]", to, subject, html });
};
// SendGrid
export const sendMail = async ({ to, subject, html }) => {
await sgMail.send({ from: "[email protected]", to, subject, html });
};📖 API Reference
POST /auth/signup
// Request
{
"name": "John Doe",
"username": "johndoe", // 5–20 chars, unique
"email": "[email protected]",
"password": "Secret@123", // 8+ chars, upper+lower+number+special
"role": "user" // must be in allowedRoles
}
// 201 — Success
{
"success": true,
"message": "Signup successful",
"data": { "_id": "...", "name": "John Doe", "email": "[email protected]" }
}
// Errors → 400 validation | 409 email/username existsPOST /auth/signin
// Request
{ "email": "[email protected]", "password": "Secret@123" }
// 200 — Success
{
"success": true,
"data": {
"user": { ... },
"accessToken": "eyJhbGci...", // 15 min expiry
"refreshToken": "eyJhbGci..." // 7 day expiry
}
}
// 200 — if 2FA is enabled
{ "success": true, "data": { "twoFactorRequired": true, "userId": "..." } }
// Errors → 401 invalid creds | 403 locked/deactivated | 400 unverifiedSignin flow:
Request → timing-safe lookup → active/deleted check → lockout check
→ password compare → attempt tracking → 2FA check
→ token generation → session create → cookie set → responsePOST /auth/refresh-token
// Reads from cookie automatically — or pass in body
{ "refreshToken": "eyJhbGci..." }
// 200 — new tokens issued, old token immediately invalidated
{ "success": true, "data": { "accessToken": "...", "refreshToken": "..." } }POST /auth/2fa/generate
Requires authenticated user — set
req.uservia yourisAuthmiddleware.
// 200
{
"success": true,
"data": {
"qrCode": "data:image/png;base64,...", // render as <img>
"secret": "JBSWY3DPEHPK3PXP",
},
}Passkey Routes
POST /passkey/register/start → WebAuthn registration options
POST /passkey/register/verify → { credential } from @simplewebauthn/browser
POST /passkey/login/start → { email }
POST /passkey/login/verify → { email, credential }🗃️ Required Schemas
User Model
import mongoose from "mongoose";
import bcrypt from "bcryptjs";
import jwt from "jsonwebtoken";
const userSchema = new mongoose.Schema(
{
name: { type: String, required: true, trim: true },
username: { type: String, required: true, unique: true, trim: true },
email: { type: String, required: true, unique: true, lowercase: true },
password: { type: String, required: true, select: false },
role: { type: String, default: "user" },
isVerified: { type: Boolean, default: false },
isActive: { type: Boolean, default: true },
isDeleted: { type: Boolean, default: false },
loginAttempts: { type: Number, default: 0 },
lockUntil: { type: Date },
twoFactorEnabled: { type: Boolean, default: false },
twoFactorSecret: { type: String, select: false },
passkeys: [
{
credentialID: String,
publicKey: String,
counter: Number,
deviceName: String,
transports: [String],
},
],
currentChallenge: { type: String, select: false },
refreshToken: { type: String, select: false },
lastLogin: { type: Date },
},
{ timestamps: true },
);
userSchema.pre("save", async function (next) {
if (!this.isModified("password")) return next();
this.password = await bcrypt.hash(this.password, 12);
next();
});
// ⚠️ These methods are required by AuthStack
userSchema.methods.isPasswordMatched = async function (pwd) {
return bcrypt.compare(pwd, this.password);
};
userSchema.methods.generateAccessToken = function () {
return jwt.sign({ _id: this._id }, process.env.JWT_SECRET, {
expiresIn: "15m",
});
};
userSchema.methods.generateRefreshToken = function () {
return jwt.sign({ _id: this._id }, process.env.JWT_SECRET, {
expiresIn: "7d",
});
};
userSchema.methods.incrementLoginAttempts = async function () {
this.loginAttempts += 1;
if (this.loginAttempts >= 5)
this.lockUntil = new Date(Date.now() + 30 * 60 * 1000);
await this.save({ validateBeforeSave: false });
};
userSchema.methods.resetLoginAttempts = async function () {
this.loginAttempts = 0;
this.lockUntil = undefined;
await this.save({ validateBeforeSave: false });
};
export default mongoose.model("User", userSchema);Session Model
import mongoose from "mongoose";
export default mongoose.model(
"Session",
new mongoose.Schema(
{
user: {
type: mongoose.Schema.Types.ObjectId,
ref: "User",
required: true,
},
email: String,
refreshToken: String,
deviceFingerprint: String,
IP: String,
userAgent: String,
lastUsed: { type: Date, default: Date.now },
},
{ timestamps: true },
),
);🛡️ Security
| Layer | Protection |
| ----------------- | ---------------------------------------------------- |
| 🔒 Passwords | bcryptjs — 12 salt rounds |
| 🎭 Timing attacks | Dummy hash compare — no user enumeration |
| 🔨 Brute force | Attempt counter + 30 min auto lockout after 5 fails |
| 🔄 Token rotation | Refresh token invalidated on every use |
| 🍪 Cookies | httpOnly + secure + sameSite — no XSS access |
| 📱 2FA | TOTP via speakeasy — Google Authenticator compatible |
| 🪪 Passkeys | FIDO2 / WebAuthn Level 2 — phishing resistant |
| 🖥️ Sessions | Device fingerprint + IP + user agent per login |
| 📧 Email enum | Forgot password returns same response always |
❓ FAQ
🤝 Contributing
PRs are welcome!
git clone https://github.com/AKASHPATEL123500/AuthStack
cd AuthStack
npm install- Fork → branch → commit → PR
- Open an issue first for major changes
- Follow existing code style
📄 License
MIT © Akash Patel
AuthStack — auth done right, the first time.
npm · GitHub · Issues
