Security Guide
HazelJS provides comprehensive, production-ready security features built right into the framework. This guide covers all available security features and how to use them effectively.
Overview
HazelJS includes enterprise-grade security middleware out of the box:
- Security Headers - Automatic protection headers
- Rate Limiting - Prevent API abuse and DDoS attacks
- CSRF Protection - Cross-Site Request Forgery prevention
- Input Sanitization - XSS and injection attack prevention
- Request Validation - Type-safe input validation
- Authentication & Authorization - Built-in guards system
All security features are enabled by default and require minimal configuration.
Security Headers
Security headers protect your application from various attacks like clickjacking, MIME sniffing, and XSS. HazelJS automatically applies security headers with sensible defaults.
Basic Usage
import { HazelApp } from '@hazeljs/core';
import { SecurityHeadersMiddleware } from '@hazeljs/core';
const app = await HazelApp.create(AppModule);
// Enable security headers with defaults
app.useGlobalMiddleware(new SecurityHeadersMiddleware());
await app.listen(3000);
Default Headers Applied:
X-Content-Type-Options: nosniff- Prevents MIME type sniffingX-Frame-Options: DENY- Prevents clickjackingX-XSS-Protection: 1; mode=block- Enables browser XSS filterReferrer-Policy: no-referrer-when-downgrade- Controls referrer informationX-Powered-Byheader removed - Hides framework information
Advanced Configuration
import { SecurityHeadersMiddleware } from '@hazeljs/core';
app.useGlobalMiddleware(new SecurityHeadersMiddleware({
// Content Security Policy
contentSecurityPolicy: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'", "'unsafe-inline'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'data:', 'https:'],
connectSrc: ["'self'"],
fontSrc: ["'self'"],
objectSrc: ["'none'"],
mediaSrc: ["'self'"],
frameSrc: ["'none'"],
upgradeInsecureRequests: true,
},
// Strict Transport Security (HSTS)
hsts: {
maxAge: 31536000, // 1 year
includeSubDomains: true,
preload: true,
},
// Referrer Policy
referrerPolicy: 'strict-origin-when-cross-origin',
// Permissions Policy
permissionsPolicy: {
geolocation: ['()'],
camera: ['()'],
microphone: ['()'],
payment: ['()'],
},
// Frame Options
frameOptions: 'DENY', // or 'SAMEORIGIN'
// XSS Protection
xssProtection: true,
// Hide Powered-By header
hidePoweredBy: true,
}));
Production Example
const securityHeaders = new SecurityHeadersMiddleware({
noSniff: true,
frameOptions: 'DENY',
xssProtection: true,
hsts: {
maxAge: 31536000,
includeSubDomains: true,
},
contentSecurityPolicy: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'https:'],
connectSrc: ["'self'"],
upgradeInsecureRequests: true,
},
hidePoweredBy: true,
});
app.useGlobalMiddleware(securityHeaders);
Rate Limiting
Rate limiting protects your API from abuse, DDoS attacks, and ensures fair resource usage. HazelJS provides flexible rate limiting with in-memory and Redis support.
Basic Usage
import { RateLimitMiddleware } from '@hazeljs/core';
// Limit to 100 requests per 15 minutes per IP
app.useGlobalMiddleware(new RateLimitMiddleware({
max: 100,
windowMs: 15 * 60 * 1000, // 15 minutes
}));
Advanced Configuration
import { RateLimitMiddleware } from '@hazeljs/core';
app.useGlobalMiddleware(new RateLimitMiddleware({
max: 10, // Maximum requests
windowMs: 60 * 1000, // Time window (1 minute)
// Custom key generation (e.g., by user ID)
keyGenerator: (req) => {
const userId = req.headers['x-user-id'];
return userId || req.socket?.remoteAddress || 'unknown';
},
// Custom message
message: 'Too many requests, please try again later.',
// HTTP status code
statusCode: 429,
// Standard rate limit headers (RFC 6585)
standardHeaders: true,
// Legacy X-RateLimit-* headers
legacyHeaders: true,
// Skip successful requests from rate limit
skipSuccessfulRequests: false,
// Skip failed requests from rate limit
skipFailedRequests: false,
}));
Route-Specific Rate Limiting
import { Controller, Post, UseMiddleware } from '@hazeljs/core';
import { RateLimitMiddleware } from '@hazeljs/core';
@Controller('/auth')
export class AuthController {
// Stricter rate limit for login
@Post('/login')
@UseMiddleware(new RateLimitMiddleware({
max: 5,
windowMs: 15 * 60 * 1000, // 5 attempts per 15 minutes
message: 'Too many login attempts. Please try again later.',
}))
async login(@Body() credentials: LoginDto) {
// Login logic
}
// Normal rate limit for other endpoints
@Post('/register')
@UseMiddleware(new RateLimitMiddleware({
max: 10,
windowMs: 60 * 1000, // 10 requests per minute
}))
async register(@Body() userData: RegisterDto) {
// Registration logic
}
}
Redis-Based Rate Limiting
For distributed applications, use Redis for shared rate limiting:
import { RateLimitMiddleware } from '@hazeljs/core';
// Custom Redis store
class RedisRateLimitStore implements RateLimitStore {
constructor(private redis: RedisClient) {}
async increment(key: string): Promise<number> {
// Implement Redis increment logic
return this.redis.incr(key);
}
async reset(key: string): Promise<void> {
await this.redis.del(key);
}
}
const redisStore = new RedisRateLimitStore(redisClient);
app.useGlobalMiddleware(new RateLimitMiddleware({
max: 100,
windowMs: 15 * 60 * 1000,
store: redisStore,
}));
Rate Limit Headers
The middleware automatically sets standard rate limit headers:
RateLimit-Limit: 100
RateLimit-Remaining: 95
RateLimit-Reset: 2025-12-14T20:00:00.000Z
CSRF Protection
CSRF (Cross-Site Request Forgery) protection prevents unauthorized actions on behalf of authenticated users. HazelJS provides built-in CSRF middleware.
Basic Usage
import { CsrfMiddleware } from '@hazeljs/core';
app.useGlobalMiddleware(new CsrfMiddleware({
cookieName: '_csrf',
headerName: 'x-csrf-token',
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
}));
Frontend Integration
React/Next.js Example:
// Get CSRF token from cookie or meta tag
const getCsrfToken = () => {
// Option 1: From cookie
const cookies = document.cookie.split(';');
const csrfCookie = cookies.find(c => c.trim().startsWith('_csrf='));
return csrfCookie?.split('=')[1];
// Option 2: From meta tag (if set by server)
// return document.querySelector('meta[name="csrf-token"]')?.content;
};
// Include in requests
fetch('/api/users', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-Token': getCsrfToken(),
},
credentials: 'include', // Important for cookies
body: JSON.stringify({ name: 'John' }),
});
HTML Form Example:
<form action="/api/users" method="POST">
<input type="hidden" name="_csrf" value="{{csrfToken}}">
<!-- or -->
<input type="hidden" name="x-csrf-token" value="{{csrfToken}}">
<input type="text" name="name" required>
<button type="submit">Submit</button>
</form>
Exclude Specific Paths
app.useGlobalMiddleware(new CsrfMiddleware({
excludePaths: [
'/webhook/stripe', // Webhook endpoints
'/api/public', // Public APIs
'/health', // Health checks
],
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
}));
Advanced Configuration
app.useGlobalMiddleware(new CsrfMiddleware({
cookieName: '_csrf',
headerName: 'x-csrf-token',
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
excludePaths: ['/webhook'],
cookieOptions: {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'strict',
path: '/',
},
}));
Input Sanitization
Always sanitize user input to prevent XSS and injection attacks. HazelJS provides comprehensive sanitization utilities.
String Sanitization
import { sanitizeString, sanitizeHtml, escapeHtml } from '@hazeljs/core';
// Sanitize plain text (removes dangerous characters)
const clean = sanitizeString(userInput);
// Sanitize HTML (allows safe HTML, removes dangerous tags/attributes)
const cleanHtml = sanitizeHtml(userInput);
// Escape HTML entities (for plain text display)
const escaped = escapeHtml(userInput);
Object Sanitization
import { sanitizeObject } from '@hazeljs/core';
@Post()
async createUser(@Body() userData: CreateUserDto) {
// Sanitize all string fields in the object
const sanitized = sanitizeObject(userData, {
sanitizeStrings: true,
sanitizeHtml: false,
allowedKeys: ['name', 'email', 'bio'],
});
return this.userService.create(sanitized);
}
URL and Email Validation
import { sanitizeUrl, sanitizeEmail } from '@hazeljs/core';
// Sanitize URLs (returns empty string if invalid)
const safeUrl = sanitizeUrl(userInput);
// Sanitize emails (returns empty string if invalid)
const safeEmail = sanitizeEmail(userInput);
Automatic Sanitization with Pipes
import { Controller, Post, Body, UsePipes } from '@hazeljs/core';
import { SanitizePipe } from '@hazeljs/core';
@Controller('/users')
export class UserController {
@Post()
@UsePipes(new SanitizePipe())
async create(@Body() userData: CreateUserDto) {
// userData is automatically sanitized
return this.userService.create(userData);
}
}
Request Validation
Use DTOs with class-validator for automatic validation and type safety.
DTO Definition
import { IsString, IsEmail, MinLength, MaxLength, IsOptional } from 'class-validator';
import { Expose } from 'class-transformer';
export class CreateUserDto {
@Expose()
@IsString()
@MinLength(2)
@MaxLength(50)
name: string;
@Expose()
@IsEmail()
email: string;
@Expose()
@IsString()
@MinLength(8)
@MaxLength(100)
password: string;
@Expose()
@IsOptional()
@IsString()
@MaxLength(500)
bio?: string;
}
Controller Usage
import { Controller, Post, Body, UsePipes } from '@hazeljs/core';
import { ValidationPipe } from '@hazeljs/core';
@Controller('/users')
export class UserController {
@Post()
@UsePipes(ValidationPipe)
async create(@Body() user: CreateUserDto) {
// user is automatically validated
// Returns 400 Bad Request if validation fails
return this.userService.create(user);
}
}
Custom Validation
import { IsString, ValidateIf, Matches } from 'class-validator';
export class UpdateUserDto {
@IsString()
@MinLength(2)
name?: string;
@ValidateIf(o => o.email)
@IsEmail()
email?: string;
@ValidateIf(o => o.phone)
@Matches(/^\+?[1-9]\d{1,14}$/)
phone?: string;
}
SQL Injection Prevention
Use Prisma (Recommended)
Prisma automatically uses parameterized queries, preventing SQL injection:
import { PrismaService } from '@hazeljs/prisma';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async findByEmail(email: string) {
// Safe - Prisma uses parameterized queries
return this.prisma.user.findUnique({
where: { email },
});
}
async searchUsers(query: string) {
// Safe - Prisma escapes all inputs
return this.prisma.user.findMany({
where: {
OR: [
{ name: { contains: query } },
{ email: { contains: query } },
],
},
});
}
}
If Using Raw SQL (Not Recommended)
// WARNING: Only use if absolutely necessary
// Always prefer Prisma's parameterized queries
import { PrismaService } from '@hazeljs/prisma';
@Injectable()
export class UserService {
constructor(private prisma: PrismaService) {}
async customQuery(userId: string) {
// Use Prisma's $queryRaw with parameters
return this.prisma.$queryRaw`
SELECT * FROM users WHERE id = ${userId}
`;
}
}
XSS Prevention
1. Sanitize HTML Input
import { sanitizeHtml, escapeHtml } from '@hazeljs/core';
// For user-generated HTML content (e.g., rich text editor)
const safeHtml = sanitizeHtml(userInput);
// For plain text that will be displayed
const safeText = escapeHtml(userInput);
2. Use Content Security Policy
app.useGlobalMiddleware(new SecurityHeadersMiddleware({
contentSecurityPolicy: {
defaultSrc: ["'self'"],
scriptSrc: ["'self'"], // No 'unsafe-inline' or 'unsafe-eval'
styleSrc: ["'self'", "'unsafe-inline'"],
imgSrc: ["'self'", 'https:'],
connectSrc: ["'self'"],
},
}));
3. Set Proper Content Types
HazelJS automatically sets Content-Type: application/json for JSON responses, which helps prevent XSS.
Authentication & Authorization
HazelJS provides a comprehensive guards system for authentication and authorization.
JWT Authentication
import { UseGuards } from '@hazeljs/core';
import { JwtAuthGuard } from '@hazeljs/auth';
@Controller('/users')
export class UserController {
@Get('/profile')
@UseGuards(JwtAuthGuard)
getProfile(@Request() req) {
return req.user; // User from JWT token
}
}
Role-Based Access Control
import { UseGuards } from '@hazeljs/core';
import { JwtAuthGuard, RolesGuard } from '@hazeljs/auth';
import { Roles } from '@hazeljs/auth';
@Controller('/admin')
@UseGuards(JwtAuthGuard, RolesGuard)
export class AdminController {
@Get('/users')
@Roles('ADMIN')
getAllUsers() {
// Only admins can access
}
}
See the Guards Guide for more details.
Complete Security Setup
Here's a complete example of setting up all security features:
import { HazelApp } from '@hazeljs/core';
import {
SecurityHeadersMiddleware,
RateLimitMiddleware,
CsrfMiddleware,
} from '@hazeljs/core';
import { AppModule } from './app.module';
const app = await HazelApp.create(AppModule);
// 1. Security Headers
app.useGlobalMiddleware(new SecurityHeadersMiddleware({
noSniff: true,
frameOptions: 'DENY',
xssProtection: true,
hsts: {
maxAge: 31536000,
includeSubDomains: true,
},
hidePoweredBy: true,
}));
// 2. Rate Limiting
app.useGlobalMiddleware(new RateLimitMiddleware({
max: 100,
windowMs: 15 * 60 * 1000,
message: 'Too many requests, please try again later.',
}));
// 3. CSRF Protection
app.useGlobalMiddleware(new CsrfMiddleware({
cookieName: '_csrf',
headerName: 'x-csrf-token',
methods: ['POST', 'PUT', 'PATCH', 'DELETE'],
excludePaths: ['/webhook', '/health'],
}));
await app.listen(3000);
Best Practices
1. Always Validate Input
@Post()
@UsePipes(ValidationPipe)
async create(@Body(CreateUserDto) user: CreateUserDto) {
// Validation happens automatically
}
2. Use Environment Variables
// ✅ Good
const jwtSecret = process.env.JWT_SECRET;
const dbUrl = process.env.DATABASE_URL;
// ❌ Bad
const jwtSecret = 'my-secret-key';
3. Implement Proper Error Handling
import { Catch, HttpError } from '@hazeljs/core';
@Catch(HttpError)
export class GlobalExceptionFilter implements ExceptionFilter<HttpError> {
catch(exception: HttpError, host: ArgumentsHost) {
const ctx = host.switchToHttp();
const response = ctx.getResponse();
// Don't expose internal errors
const message = exception.statusCode >= 500
? 'Internal server error'
: exception.message;
response.status(exception.statusCode).json({
statusCode: exception.statusCode,
message,
timestamp: new Date().toISOString(),
});
}
}
4. Log Security Events
import logger from '@hazeljs/core';
// Log failed login attempts
logger.warn('Failed login attempt', {
ip: req.socket?.remoteAddress,
username: userInput.username,
// Never log passwords!
});
// Log rate limit violations
logger.warn('Rate limit exceeded', {
ip: req.socket?.remoteAddress,
path: req.url,
});
5. Keep Dependencies Updated
# Check for vulnerabilities
npm audit
# Fix vulnerabilities
npm audit fix
# Update dependencies
npm update
Security Checklist
Before deploying to production, ensure:
- Security headers middleware enabled
- Rate limiting configured
- CSRF protection enabled for state-changing operations
- Input validation on all endpoints
- Input sanitization for user-generated content
- Authentication and authorization implemented
- HTTPS enabled in production
- Environment variables used for secrets
- Dependencies regularly updated
- Security events logged
- Error messages don't expose sensitive information
- File uploads validated and scanned
- SQL injection prevented (use Prisma)
- XSS prevented (sanitize output)
Additional Resources
- OWASP Top 10
- Node.js Security Best Practices
- HazelJS Security Policy
- Guards Guide - Authentication and authorization
- Middleware Guide - Advanced middleware usage