nestjs-security-module
v1.1.1
Published
A plug-and-play NestJS security module with CORS, Helmet, rate limiting, audit logging, CSP, XSS sanitization, and more.
Maintainers
Readme
NestJS Security Module 🔐
A plug-and-play security module for NestJS, bundling best-practice HTTP headers, CORS, rate-limiting, audit logging, CSP, XSS sanitization and more.
Table of Contents
- Features
- Installation
- Basic Usage
- Async / Env-Based Configuration
- Options Reference
- CORS Configuration
- Example
.env - Troubleshooting
Features
- 🔒 Helmet integration for standard security headers
- 🌐 Enhanced CORS support with preflight request handling and case-sensitive headers
- 🛡️ Rate Limiting (per-IP)
- 📋 Audit Logging (to console + file)
- 🛑 Content-Security-Policy (CSP)
- 🧹 XSS Sanitization (deep sanitize middleware)
- ⚙️ Additional headers: Referrer-Policy, HSTS, Expect-CT, Permissions-Policy, COEP …and more
Installation
npm install nestjs-security-module
# or
yarn add nestjs-security-moduleBasic Usage
Import and configure the module in your AppModule:
// app.module.ts
import { Module } from '@nestjs/common';
import { SecurityModule } from 'nestjs-security-module';
@Module({
imports: [
SecurityModule.forRoot({
helmet: true,
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'HEAD', 'POST'],
allowedHeaders: ['Content-Type', 'content-type', 'Authorization', 'Accept', 'Origin', 'X-Requested-With'],
credentials: true,
},
rateLimit: { windowMs: 60_000, max: 10 },
auditLog: true,
csp: true,
sanitize: true,
referrerPolicy: true,
xFrameOptions: 'SAMEORIGIN',
hsts: true,
expectCt: true,
permissionsPolicy: { geolocation: ['self'] },
crossOriginEmbedderPolicy: true,
}),
// … your other modules
],
})
export class AppModule {}Async / Env-Based Configuration
If you prefer loading options from environment variables via @nestjs/config, use the async API:
// app.module.ts
import { Module } from '@nestjs/common';
import { ConfigModule, ConfigService } from '@nestjs/config';
import { SecurityModule } from 'nestjs-security-module';
@Module({
imports: [
ConfigModule.forRoot({ isGlobal: true }),
SecurityModule.forRootAsync({
imports: [ConfigModule],
inject: [ConfigService],
useFactory: (cfg: ConfigService) => ({
helmet: cfg.get<boolean>('SECURITY_HELMET'),
cors: cfg.get<boolean>('SECURITY_CORS')
? {
origin: cfg.get<string>('CORS_ORIGIN'),
methods: cfg.get<string>('CORS_METHODS').split(','),
allowedHeaders: ['Content-Type', 'content-type', 'Authorization', 'Accept', 'Origin', 'X-Requested-With'],
credentials: true,
}
: undefined,
rateLimit: cfg.get<boolean>('SECURITY_RATE_LIMIT')
? {
windowMs: cfg.get<number>('RATE_LIMIT_WINDOW'),
max: cfg.get<number>('RATE_LIMIT_MAX'),
}
: undefined,
auditLog: cfg.get<boolean>('SECURITY_AUDIT_LOG'),
csp: cfg.get<boolean>('SECURITY_CSP') ? { directives: { defaultSrc: ["'self'"] } } : undefined,
sanitize: cfg.get<boolean>('SECURITY_SANITIZE'),
referrerPolicy: cfg.get<boolean>('SECURITY_REFERRER'),
xFrameOptions: cfg.get<boolean>('SECURITY_XFRAME') ? 'SAMEORIGIN' : undefined,
hsts: cfg.get<boolean>('SECURITY_HSTS') ? { maxAge: parseInt(cfg.get<string>('SECURITY_HSTS_MAX_AGE')) } : undefined,
xContentTypeOptions: cfg.get<boolean>('SECURITY_XCONTENT_TYPE_OPTIONS'),
expectCt: cfg.get<boolean>('SECURITY_EXPECT_CT') ? { maxAge: parseInt(cfg.get<string>('SECURITY_EXPECT_CT_MAX_AGE')) } : undefined,
permissionsPolicy: cfg.get<boolean>('SECURITY_PERMISSIONS') ? { geolocation: ['self'] } : undefined,
crossOriginEmbedderPolicy: cfg.get<boolean>('SECURITY_COEP'),
}),
}),
],
})
export class AppModule {}Options Reference
| Option | Type | Description |
| --------------------------- | ------------------------------------------------ | ------------------------------------------- |
| helmet | boolean | Enable Helmet middleware |
| cors | boolean \| CORSConfig | Enable/customize CORS with enhanced support |
| rateLimit | { windowMs: number; max: number } | IP-based rate limiting |
| auditLog | boolean | Log requests to console + file |
| csp | boolean \| object | Enable CSP (Content Security Policy) |
| sanitize | boolean | Deep sanitize incoming payloads |
| referrerPolicy | boolean \| object | Set Referrer-Policy header |
| xFrameOptions | boolean \| 'DENY' \| 'SAMEORIGIN' | Set X-Frame-Options header |
| hsts | boolean \| object | Enforce HTTPS via Strict-Transport-Security |
| xContentTypeOptions | boolean | Prevent MIME sniffing |
| expectCt | boolean \| object | Set Expect-CT header |
| permissionsPolicy | boolean \| Record<string, string[]> | Set Permissions-Policy header |
| crossOriginEmbedderPolicy | boolean \| object | Enable COEP header |
CORS Configuration
The CORS configuration has been enhanced with the following improvements:
Enhanced CORS Options
interface CORSConfig {
origin: string;
methods: string[] | string;
allowedHeaders?: string[];
credentials?: boolean;
}Key Improvements
- ✅ Preflight Request Handling: Automatic OPTIONS request handling
- ✅ Case-Sensitive Headers: Support for both
Content-Typeandcontent-type - ✅ Array/String Methods: Support for both array and string method definitions
- ✅ Credentials Support: Proper handling of credentials in CORS requests
- ✅ Max-Age Caching: 24-hour preflight response caching
Example CORS Configuration
cors: {
origin: 'http://localhost:3000',
methods: ['GET', 'HEAD', 'POST'], // Array format
// or methods: 'GET,HEAD,POST', // String format
allowedHeaders: ['Content-Type', 'content-type', 'Authorization', 'Accept', 'Origin', 'X-Requested-With'],
credentials: true,
}Manual OPTIONS Endpoint (Optional)
For additional control, you can add a manual OPTIONS endpoint:
// app.controller.ts
import { Controller, Options, Res } from '@nestjs/common';
import { Response } from 'express';
@Controller()
export class AppController {
@Options()
handleOptions(@Res() res: Response): void {
res.setHeader('Access-Control-Allow-Origin', 'http://localhost:3000');
res.setHeader('Access-Control-Allow-Methods', 'GET,HEAD,POST,OPTIONS');
res.setHeader('Access-Control-Allow-Headers', 'Content-Type,content-type,Authorization,Accept,Origin,X-Requested-With');
res.setHeader('Access-Control-Allow-Credentials', 'true');
res.setHeader('Access-Control-Max-Age', '86400');
res.status(200).end();
}
}Example .env
SECURITY_HELMET=true
SECURITY_CORS=true
CORS_ORIGIN=http://localhost:3000
CORS_METHODS=GET,HEAD,POST
SECURITY_RATE_LIMIT=true
RATE_LIMIT_WINDOW=60000
RATE_LIMIT_MAX=10
SECURITY_AUDIT_LOG=true
SECURITY_CSP=true
SECURITY_SANITIZE=true
SECURITY_REFERRER=true
SECURITY_XFRAME=true
SECURITY_HSTS=true
SECURITY_HSTS_MAX_AGE=31536000
SECURITY_XCONTENT_TYPE_OPTIONS=true
SECURITY_EXPECT_CT=true
SECURITY_EXPECT_CT_MAX_AGE=30
SECURITY_PERMISSIONS=true
SECURITY_COEP=trueTroubleshooting
CORS Issues
If you encounter CORS errors, ensure:
- Origin is correctly set: Use specific origin instead of wildcard
* - Headers are case-sensitive: Include both
Content-Typeandcontent-type - Methods are properly formatted: Use array format for better compatibility
- Credentials are enabled: Set
credentials: truefor authenticated requests
Rate Limiting
- Rate limiting is per-IP address
- Default: 10 requests per 60 seconds
- Configure via
RATE_LIMIT_WINDOWandRATE_LIMIT_MAXenvironment variables
Security Headers
All security headers are automatically applied when enabled:
Content-Security-PolicyStrict-Transport-SecurityX-Frame-OptionsX-Content-Type-OptionsReferrer-PolicyPermissions-PolicyCross-Origin-Embedder-Policy
Testing
Test your CORS configuration:
# Test preflight request
curl -v -H "Origin: http://localhost:3000" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type" \
-X OPTIONS http://localhost:3001
# Test regular request
curl -v -H "Origin: http://localhost:3000" \
-H "Content-Type: application/json" \
-X POST http://localhost:3001Contributing
This module includes enhanced CORS support and improved security features. For issues or contributions, please refer to the project repository.
