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 sniffing
  • X-Frame-Options: DENY - Prevents clickjacking
  • X-XSS-Protection: 1; mode=block - Enables browser XSS filter
  • Referrer-Policy: no-referrer-when-downgrade - Controls referrer information
  • X-Powered-By header 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