A plug-and-play security module for NestJS, bundling best-practice HTTP headers, CORS, rate-limiting, audit logging, CSP, XSS sanitization and more.
- Features
- Installation
- Basic Usage
- Async / Env-Based Configuration
- Options Reference
- CORS Configuration
- Example
.env - Troubleshooting
- 🔒 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
npm install nestjs-security-module
# or
yarn add nestjs-security-moduleImport 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 {}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 {}| 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 |
The CORS configuration has been enhanced with the following improvements:
interface CORSConfig {
origin: string;
methods: string[] | string;
allowedHeaders?: string[];
credentials?: boolean;
}- ✅ 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
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,
}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();
}
}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=trueIf 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 is per-IP address
- Default: 10 requests per 60 seconds
- Configure via
RATE_LIMIT_WINDOWandRATE_LIMIT_MAXenvironment variables
All security headers are automatically applied when enabled:
Content-Security-PolicyStrict-Transport-SecurityX-Frame-OptionsX-Content-Type-OptionsReferrer-PolicyPermissions-PolicyCross-Origin-Embedder-Policy
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:3001This module includes enhanced CORS support and improved security features. For issues or contributions, please refer to the project repository.