Unified security layer for Express.js: authentication, validation, sanitization, rate limiting, CORS, lockout, CSRF protection and request tracing
npm install hi-defendjsOne-line security for Express.js
DefendJS unifies authentication, validation, sanitization, rate-limiting, headers, CSRF protection, request tracing and parsing
into a single, consistent security layer for Express applications.
Modern Express applications require multiple security libraries to handle authentication,
password hashing, validation, sanitization, rate limiting, headers, compression and parsing.
Managing these separately leads to duplicated logic, configuration drift and subtle bugs.
DefendJS solves this by acting as a single orchestration layer.
| Capability | Status | Notes |
|---|---|---|
| JWT Authentication | Stable | Issuer, audience, expiry and subject supported |
| Password Hashing | Stable | Argon2 primary with bcrypt fallback |
| Google Login | Stable | ID token verification adapter included |
| Route Protection (RBAC) | Stable | Role-based access via JWT payload |
| Validation | Stable | Zod and express-validator supported |
| Sanitization | Stable | HTML injection and XSS protection |
| Rate Limiting | Stable | Presets and per-route configuration |
| CORS & Headers | Stable | Helmet, HPP and CORS integrated |
| Compression | Stable | gzip via a single flag |
| Logging | Improved | Structured, lifecycle-aware logs with adapter, manager and fallback visibility. Designed for production debugging without leaking sensitive data. |
| Account Lockout | Stable | Brute-force protection for login endpoints |
npm install hi-defendjsimport express from "express";
import { DefendJS } from "hi-defendjs";const app = express();
app.use(DefendJS.middleware("api"));
app.listen(3000);
const hash = await DefendJS.hash(password);
const isValid = await DefendJS.verify(password, hash);
router.post(
"/register",
DefendJS.validate([...]),
controller
);
DefendJS.rateLimit({ max: 5, windowMs: 15 60 1000 });
// In your login controller
const status = await DefendJS.lockout.increment(email);if (status.isLocked) {
return res.status(429).json({
error: Account locked. Try again in ${status.retryAfter} seconds.
});
}
// ... verify password ...
// On success:
await DefendJS.lockout.reset(email);
Global CORS defines the baseline access rules for your entire application.
These rules apply to all routes unless explicitly overridden at the route level.
This is ideal for standard APIs where most endpoints share the same access policy.
Enable CORS globally using default configuration.
app.use(
DefendJS.middleware({
cors: true
})
);
Define explicit CORS rules for all routes.
app.use(
DefendJS.middleware({
cors: {
origin: ["https://app.example.com"],
methods: ["GET", "POST", "PUT", "DELETE"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true
}
})
);
Allow multiple frontends (web, admin, mobile) to access the same API.
app.use(
DefendJS.middleware({
cors: {
origin: [
"https://web.example.com",
"https://admin.example.com",
"https://mobile.example.com"
],
credentials: true
}
})
);
Use open CORS rules for public or read-only APIs.
app.use(
DefendJS.middleware({
cors: {
origin: "*",
methods: ["GET"]
}
})
);
DefendJS supports fine-grained security control at the route level.
Each capability can be configured independently without affecting global middleware.
This allows you to apply strict security where needed (auth, payments, admin)
and relaxed rules for public or internal endpoints.
Route-level CORS is useful when different consumers access different endpoints
(e.g. web app, admin panel, third-party services).
Example: Webhook endpoint (single trusted origin)
router.post(
"/webhook",
DefendJS.cors({
origin: ["https://trusted-client.com"],
methods: ["POST"],
allowedHeaders: ["Content-Type", "Authorization"],
credentials: true
}),
controller
);
Example: Admin dashboard with restricted origins
router.get(
"/admin/stats",
DefendJS.cors({
origin: [
"https://admin.example.com",
"https://internal.example.com"
],
credentials: true
}),
controller
);
Example: Public API with open read access
router.get(
"/public/feed",
DefendJS.cors({ origin: "*" }),
controller
);
DefendJS automatically detects validation strategy based on input type.
Choose the style based on complexity and ownership.
import { DefendJS , body } from "hi-defendjs";router.post(
"/register",
DefendJS.validate([
body("email")
.notEmpty()
.isEmail(),
body("password")
.isLength({ min: 6 }),
body("role")
.optional()
.isIn(["user", "admin"])
]),
controller
);
import { DefendJS , z } from "hi-defendjs";const registerSchema = z.object({
email: z.string().email(),
password: z.string().min(6),
role: z.enum(["user", "admin"]).optional()
});
router.post(
"/register",
DefendJS.validate(registerSchema),
controller
);
Both approaches produce a unified error response format.
Sanitization should reflect trust boundaries.
Not all routes require the same level of strictness.
User-generated content (allow formatting)
router.post(
"/comment",
DefendJS.sanitize({
allowedTags: ["b", "i", "strong", "em", "a"],
allowedAttributes: {
a: ["href"]
}
}),
controller
);
Strict input (no HTML allowed)
router.post(
"/feedback",
DefendJS.sanitize({
allowedTags: [],
allowedAttributes: {}
}),
controller
);
Trusted internal pipeline (disable sanitization)
router.post(
"/internal/import",
DefendJS.sanitize(false),
controller
);
A real-world admin route combining multiple security layers.
Execution order is deterministic and isolated to the route.
router.post(
"/admin/create-user",
DefendJS.auth({ roles: ["admin"] }),
DefendJS.rateLimit({ max: 3, windowMs: 10 60 1000 }),
DefendJS.cors({
origin: ["https://admin.example.com"]
}),
DefendJS.sanitize(),
DefendJS.validate([
body("email").isEmail(),
body("password").isLength({ min: 8 })
]),
controller
);
JWT support is optional. Enable it only if you want authentication features.
DefendJS.resetInstance();DefendJS.getInstance({
auth: {
enabled: true,
jwtSecret: process.env.JWT_SECRET,
jwtExpiresIn: "1d"
}
});
This section demonstrates a complete, production-ready authentication setup using DefendJS.
It covers signup, JWT login, Google login, role-based access control, and proper initialization.
import express from "express";
import dotenv from "dotenv";
import { DefendJS } from "hi-defendjs";
import authRoutes from "./routes/auth.routes.js";dotenv.config();
const app = express();
DefendJS.resetInstance();
DefendJS.getInstance({
auth: {
enabled: true,
jwtSecret: process.env.JWT_SECRET || "supersecret_32_chars_minimum",
jwtExpiresIn: "1d",
googleClientId: process.env.GOOGLE_CLIENT_ID // this only added if need googleLogin as well
}
});
app.use(DefendJS.middleware("api"));
app.use("/auth", authRoutes);
app.listen(3000);
Note: resetInstance() is recommended only for tests or starter templates.
It should not be used repeatedly in production runtime.
import { Router } from "express";
import {
signup,
loginWithJwt,
loginWithGoogle
} from "../controllers/auth.controller.js";
import { DefendJS } from "hi-defendjs";const router = Router();
router.post("/signup", signup);
router.post("/login", loginWithJwt);
router.post("/google", loginWithGoogle);
router.get(
"/me",
DefendJS.auth(),
(req, res) => res.json({ user: req.user })
);
export default router;
import { DefendJS } from "hi-defendjs";
import { HttpError } from "../core/errors/HttpError.js";
import User from "../models/User.js";
const JWT_OPTIONS = {
issuer: 'hi-defendjs-backend',
audience: ['web-app', 'mobile-app'],
expiresIn: '7d',
subject: 'user-authentication'
};
exports.registerUser = async(req, res) => {
try {
const { name, email, password } = req.body;
const existingUser = await User.findOne({ email });
if (existingUser) {
return res.status(400).json({
error: 'User already exists'
});
}
const hashedPassword = await DefendJS.hash(password);
const user = await User.create({
name,
email,
password: hashedPassword
});
const token = DefendJS.jwt.sign({
userId: user._id.toString(),
email: user.email,
name: user.name,
role: 'user'
},
JWT_OPTIONS
);
res.status(201).json({
message: 'User registered successfully',
token,
user: {
id: user._id,
name: user.name,
email: user.email
}
});
} catch (error) {
console.error('Registration error:', error);
res.status(500).json({
error: 'Registration failed',
details: error.message
});
}
};
exports.loginUser = async(req, res) => {
try {
const { email, password } = req.body;
const user = await User.findOne({ email });
if (!user) {
return res.status(401).json({
error: 'Invalid credentials'
});
}
const isValid = await DefendJS.verify(password, user.password);
if (!isValid) {
return res.status(401).json({
error: 'Invalid credentials'
});
}
const token = DefendJS.jwt.sign({
userId: user._id.toString(),
email: user.email,
name: user.name,
role: 'user'
},
JWT_OPTIONS
);
res.json({
message: 'Login successful',
token,
user: {
id: user._id,
name: user.name,
email: user.email
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({
error: 'Login failed',
details: error.message
});
}
};
app.get(
"/admin",
DefendJS.auth({ roles: ["admin"] }),
(req, res) => {
res.json({ message: "Welcome Admin" });
}
);
const router = express.Router();
router.post(
'/register',DefendJS.validate([
body("name")
.notEmpty().withMessage("Name is required")
.isLength({ min: 3 }).withMessage("Name must be at least 3 characters"),body("email")
.notEmpty().withMessage("Email is required")
.isEmail().withMessage("Invalid email format"),body("password")
.notEmpty().withMessage("Password is required")
.isLength({ min: 6 }).withMessage("Password must be at least 6 characters"),
]),registerUser
);router.post(
'/login',DefendJS.validate([
body("email")
.notEmpty().withMessage("Email is required")
.isEmail().withMessage("Invalid email format"),body("password")
.notEmpty().withMessage("Password is required")
]),DefendJS.rateLimit({ max: 5, windowMs: 15 60 1000 }),
loginUser
);router.get(
'/profile',
DefendJS.auth({ required: true }),
getProfile
);
router.post('/create', DefendJS.auth({ required: true }), createTask)
router.get('/get', DefendJS.auth({ required: true }), getTask)
router.put('/:id', DefendJS.auth({ required: true }), updateTask)
router.psot('/health',heatlh);
DefendJS does not require JWT options for most use cases.
Default configuration provided during initialization is sufficient.
DefendJS.getInstance({
auth: {
enabled: true,
jwtSecret: process.env.JWT_SECRET,
jwtExpiresIn: "1d"
}
});
Advanced JWT options can be provided only when needed:
DefendJS.jwt.sign(
{
userId: user.id,
roles: user.roles
},
{
issuer: "my-app",
audience: ["web", "mobile"],
subject: "user-auth",
expiresIn: "7d"
}
);
JWT options are optional and intended for advanced authentication scenarios.
DefendJS provides a complete, opinionated security layer for Express.
It focuses on correctness, safety and developer productivity.
One dependency. One middleware. Complete security.
Advanced patterns, RBAC strategies, adapter extensions and deployment guides
will be added over time.