nest_12_security - Security Best Practices
Table of Contents
- helmet - security headers
- CORS - cross-origin resource sharing
- CSRF protection
- rate limiting with @nestjs/throttler
- input validation and injection prevention
- dependency scanning
- secure defaults checklist
- prerequisites
Your NestJS app is a target Every endpoint , every dependency , every misconfigured CORS policy is a potential entry point The framework gives you tools - helmet for headers , throttler for rate limiting , pipes for validation - but only if you actually use them This section covers the security layers you need before you deploy anything that faces the internet
what’s in here
- Helmet for security headers
- CORS configuration (don’t use wildcards in prod)
- CSRF protection
- Rate limiting with @nestjs/throttler
- Input validation and SQL injection prevention
- Dependency scanning
- Secure defaults checklist
helmet - security headers
npm install helmet
// main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import helmet from 'helmet'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.use(helmet())
// sets:
// Content-Security-Policy
// X-Content-Type-Options: nosniff
// X-Frame-Options: SAMEORIGIN
// X-XSS-Protection: 0 (deprecated but still relevant)
// Strict-Transport-Security (if HTTPS)
// Referrer-Policy
// Permissions-Policy
await app.listen(3000)
}
Helmet sets ~15 HTTP security headers that browsers respect
Content-Security-Policy prevents XSS by restricting what resources can load
X-Content-Type-Options prevents MIME type sniffing
Strict-Transport-Security enforces HTTPS
API-only apps don’t need CSP as much , but the rest still matters Always install helmet - it’s one line and covers the most common header-based vulnerabilities
CORS - cross-origin resource sharing
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.enableCors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || [],
// BAD: origin: '*' - opens your API to any website
methods: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH'],
allowedHeaders: ['Content-Type', 'Authorization'],
exposedHeaders: ['X-Correlation-Id'],
credentials: true,
maxAge: 3600 // cache preflight for 1 hour
})
await app.listen(3000)
}
Never use origin: '*' in production - it allows any website to make authenticated requests from a user’s browser
Specify exact origins your frontend runs on: ['https://app.example.com', 'https://admin.example.com']
credentials: true is required for cookies/auth headers - but only works with explicit origins , not wildcards
For development , use your localhost: ['http://localhost:4200', 'http://localhost:3000']
CSRF protection
npm install csurf @types/csurf
CSRF tokens protect against attacks where a malicious site makes authenticated requests using the victim’s cookies
// main.ts
import * as csurf from 'csurf'
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
async function bootstrap() {
const app = await NestFactory.create(AppModule)
app.use(csurf({ cookie: true }))
// csurf adds a CSRF token to every response
// frontend must include it as X-CSRF-Token header on mutating requests
await app.listen(3000)
}
If your API uses cookies (session-based auth) , you need CSRF protection If your API uses Authorization headers (Bearer tokens) and never sets auth cookies , CSRF doesn’t apply SPAs with JWT in localStorage don’t need CSRF - but that brings XSS risks instead. Pick your poison
rate limiting with @nestjs/throttler
npm install @nestjs/throttler
import { Module } from '@nestjs/common'
import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler'
import { APP_GUARD } from '@nestjs/core'
@Module({
imports: [
ThrottlerModule.forRoot([
{
name: 'short',
ttl: 1000, // 1 second
limit: 10 // 10 requests per second
},
{
name: 'medium',
ttl: 60000, // 1 minute
limit: 100 // 100 requests per minute
},
{
name: 'long',
ttl: 300000, // 5 minutes
limit: 300 // 300 requests per 5 minutes
}
])
],
providers: [
{
provide: APP_GUARD,
useClass: ThrottlerGuard // global rate limiting
}
]
})
export class AppModule {}
Override limits per route:
import { Controller, Post } from '@nestjs/common'
import { SkipThrottle, Throttle } from '@nestjs/throttler'
@Controller('auth')
export class AuthController {
@Post('login')
@Throttle({ short: { limit: 3 }, medium: { limit: 10 } })
// stricter limits on auth endpoints - prevent brute force
async login() {}
@Post('register')
@Throttle({ short: { limit: 2 } })
// even stricter on registration - prevent account creation abuse
async register() {}
@Get('health')
@SkipThrottle() // health checks should never be rate limited
async health() {}
}
Global throttling protects against DoS
Stricter per-route throttling on auth endpoints prevents brute force
@SkipThrottle() for health checks and monitoring endpoints
input validation and injection prevention
Pipes handle input validation (nest_09_pipes) , but injection prevention is an additional layer:
import { Injectable, PipeTransform, ArgumentMetadata, BadRequestException } from '@nestjs/common'
@Injectable()
export class NoSqlInjectionPipe implements PipeTransform {
transform(value: any, metadata: ArgumentMetadata): any {
if (typeof value === 'string') {
// block common NoSQL injection patterns
const patterns = [
/\$where/,
/\$ne/,
/\$gt/,
/\$regex/,
/\$exists/,
/".*\".*\{/
]
for (const pattern of patterns) {
if (pattern.test(value)) {
throw new BadRequestException('invalid input detected')
}
}
}
if (typeof value === 'object' && value !== null) {
for (const key of Object.keys(value)) {
if (key.startsWith('$') || key.includes('.')) {
throw new BadRequestException('invalid parameter name')
}
}
}
return value
}
}
This pipe blocks MongoDB injection operators ($where , $ne , $gt)
Apply it globally or on specific endpoints dealing with database queries
For SQL databases , use parameterized queries - NEVER concatenate user input into SQL strings
dependency scanning
# scan for known vulnerabilities in dependencies
npm audit
# OWASP dependency check
npx audit-ci --moderate
# Snyk (more comprehensive)
npm install -g snyk
snyk test
snyk monitor # continuous monitoring
Run npm audit in CI - fail builds on moderate+ vulnerabilities
Use Snyk or Dependabot for continuous monitoring
NestJS itself has a decent security track record , but your transitive dependencies (class-validator , passport , typeorm) can have CVEs
secure defaults checklist
Before deploy:
- Helmet enabled with security headers
- CORS configured with specific origins (no wildcards)
- Rate limiting active (global + stricter on auth)
- Validation pipe with whitelist=true , forbidNonWhitelisted=true
- JWT secret is a strong random value in environment variable (not in code)
- JWT tokens expire in 15 minutes or less
- Refresh tokens are revokable
- HTTPS enforced (Strict-Transport-Security header)
- Express
trust proxyconfigured if behind reverse proxy - Error responses don’t include stack traces
-
npm auditpasses with no high/critical vulnerabilities - Secrets never logged (see nest_10_filters)
// trust proxy - required when behind Nginx/ELB
// main.ts
async function bootstrap() {
const app = await NestFactory.create(AppModule)
// trust proxy headers from reverse proxy
// IMPORTANT: without this , rate limiting sees all traffic from proxy IP
app.getHttpAdapter().getInstance().set('trust proxy', 1)
await app.listen(3000)
}
Without trust proxy , your app sees all traffic coming from the proxy’s IP instead of the real client IP
Rate limiting won’t work properly , and IP-based features (geolocation , allowlists) will be wrong
prerequisites
nest_11_auth - Authentication & Passport
Posted · Updated