Cybersecurity legionary! Architect Vitruvius knows that every province of the Empire needs solid fortifications. Just as defensive walls protected Rome from invaders, SSL/TLS and HTTP security protect our NestJS applications from network attacks. It's time to learn the art of building digital defensive walls!
SSL (Secure Sockets Layer) and its successor TLS (Transport Layer Security) are cryptographic protocols ensuring secure communication over the network. HTTPS is HTTP running over the SSL/TLS layer - it encrypts data transmitted between the client and server.
The first step in building defensive walls is obtaining certificates. In a development environment, we can generate self-signed certificates, and in production, we use Let's Encrypt or commercial providers.
1// scripts/generate-dev-cert.ts
2import { execSync } from 'child_process';
3import * as fs from 'fs';
4import * as path from 'path';
5
6const certsDir = path.join(__dirname, '..', 'certs');
7
8// Create directory for certificates
9if (!fs.existsSync(certsDir)) {
10 fs.mkdirSync(certsDir, { recursive: true });
11}
12
13// Generate private key and certificate
14execSync(\`openssl req -x509 -newkey rsa:4096 \\
15 -keyout \${certsDir}/key.pem \\
16 -out \${certsDir}/cert.pem \\
17 -days 365 -nodes \\
18 -subj "/C=PL/ST=Roma/L=Roma/O=Imperium/CN=localhost"\`);
19
20console.log('Development certificates generated in the certs/ directory');1// src/main.ts - starting the server with HTTPS
2import { NestFactory } from '@nestjs/core';
3import { AppModule } from './app.module';
4import * as fs from 'fs';
5import * as path from 'path';
6
7async function bootstrap() {
8 const isProduction = process.env.NODE_ENV === 'production';
9
10 // HTTPS options - SSL certificates
11 const httpsOptions = isProduction
12 ? {
13 key: fs.readFileSync(process.env.SSL_KEY_PATH),
14 cert: fs.readFileSync(process.env.SSL_CERT_PATH),
15 ca: fs.readFileSync(process.env.SSL_CA_PATH), // Certificate chain
16 }
17 : {
18 key: fs.readFileSync(path.join(__dirname, '..', 'certs', 'key.pem')),
19 cert: fs.readFileSync(path.join(__dirname, '..', 'certs', 'cert.pem')),
20 };
21
22 const app = await NestFactory.create(AppModule, { httpsOptions });
23
24 // Force HTTP -> HTTPS redirect
25 if (isProduction) {
26 app.use((req, res, next) => {
27 if (req.headers['x-forwarded-proto'] !== 'https') {
28 return res.redirect(301, \`https://\${req.headers.host}\${req.url}\`);
29 }
30 next();
31 });
32 }
33
34 await app.listen(process.env.PORT || 3000);
35}
36bootstrap();Helmet.js is like a centurion's helmet - it protects the head (HTTP headers) against the most common attacks. It sets appropriate security headers automatically.
1// src/main.ts - Helmet configuration
2import helmet from 'helmet';
3
4async function bootstrap() {
5 const app = await NestFactory.create(AppModule);
6
7 // Basic Helmet configuration
8 app.use(helmet());
9
10 // Advanced Helmet configuration
11 app.use(helmet({
12 // Content-Security-Policy - controls where resources are loaded from
13 contentSecurityPolicy: {
14 directives: {
15 defaultSrc: ["'self'"],
16 scriptSrc: ["'self'", "'unsafe-inline'"],
17 styleSrc: ["'self'", "'unsafe-inline'", 'https://fonts.googleapis.com'],
18 fontSrc: ["'self'", 'https://fonts.gstatic.com'],
19 imgSrc: ["'self'", 'data:', 'https:'],
20 connectSrc: ["'self'", process.env.API_URL],
21 frameSrc: ["'none'"],
22 objectSrc: ["'none'"],
23 },
24 },
25 // Strict-Transport-Security - enforces HTTPS
26 hsts: {
27 maxAge: 31536000, // 1 year
28 includeSubDomains: true,
29 preload: true,
30 },
31 // X-Frame-Options - prevents clickjacking
32 frameguard: { action: 'deny' },
33 // X-Content-Type-Options - prevents MIME sniffing
34 noSniff: true,
35 // Referrer-Policy
36 referrerPolicy: { policy: 'strict-origin-when-cross-origin' },
37 }));
38}CORS (Cross-Origin Resource Sharing) controls which domains can communicate with our API. It's like a list of trusted ambassadors of the Empire.
1// src/main.ts - production CORS configuration
2import { CorsOptions } from '@nestjs/common/interfaces/external/cors-options.interface';
3
4const corsOptions: CorsOptions = {
5 origin: (origin, callback) => {
6 const allowedOrigins = process.env.CORS_ORIGINS?.split(',') || [];
7
8 // Allow requests without origin (mobile, Postman)
9 if (!origin) return callback(null, true);
10
11 if (allowedOrigins.includes(origin)) {
12 callback(null, true);
13 } else {
14 callback(new Error(\`Origin \${origin} is not allowed by CORS\`));
15 }
16 },
17 methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
18 allowedHeaders: ['Content-Type', 'Authorization', 'X-Requested-With'],
19 exposedHeaders: ['X-Total-Count', 'X-Page-Count'],
20 credentials: true,
21 maxAge: 86400, // Cache preflight for 24h
22};
23
24app.enableCors(corsOptions);Rate limiting is like guards at the city gates - they control how many people can enter at a given time, preventing a siege (DDoS attacks).
1// src/app.module.ts - ThrottlerModule configuration
2import { Module } from '@nestjs/common';
3import { ThrottlerModule, ThrottlerGuard } from '@nestjs/throttler';
4import { APP_GUARD } from '@nestjs/core';
5
6@Module({
7 imports: [
8 ThrottlerModule.forRoot([
9 {
10 name: 'short', // Short-term limit
11 ttl: 1000, // 1 second
12 limit: 3, // Max 3 requests per second
13 },
14 {
15 name: 'medium', // Medium limit
16 ttl: 10000, // 10 seconds
17 limit: 20, // Max 20 requests per 10 seconds
18 },
19 {
20 name: 'long', // Long-term limit
21 ttl: 60000, // 1 minute
22 limit: 100, // Max 100 requests per minute
23 },
24 ]),
25 ],
26 providers: [
27 {
28 provide: APP_GUARD,
29 useClass: ThrottlerGuard,
30 },
31 ],
32})
33export class AppModule {}
34
35// Disabling rate limiting for specific endpoints
36import { SkipThrottle, Throttle } from '@nestjs/throttler';
37
38@Controller('legions')
39export class LegionsController {
40 @SkipThrottle() // No limit for health check
41 @Get('health')
42 healthCheck() {
43 return { status: 'ok' };
44 }
45
46 @Throttle({ short: { ttl: 1000, limit: 1 } }) // Stricter limit
47 @Post('login')
48 login() {
49 // Login with 1 req/s limitation
50 }
51}CSRF (Cross-Site Request Forgery) is an attack where a malicious site makes requests on behalf of a logged-in user. We protect against it using CSRF tokens.
1// src/main.ts - CSRF protection with csurf
2import * as csurf from 'csurf';
3
4async function bootstrap() {
5 const app = await NestFactory.create(AppModule);
6
7 // CSRF protection (for applications with sessions/cookies)
8 app.use(csurf({
9 cookie: {
10 httpOnly: true,
11 secure: process.env.NODE_ENV === 'production',
12 sameSite: 'strict',
13 },
14 }));
15
16 // Middleware setting CSRF token in the response
17 app.use((req, res, next) => {
18 res.cookie('XSRF-TOKEN', req.csrfToken(), {
19 httpOnly: false, // Frontend must be able to read it
20 secure: process.env.NODE_ENV === 'production',
21 sameSite: 'strict',
22 });
23 next();
24 });
25}1// src/security/security.module.ts
2import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
3import { ThrottlerModule } from '@nestjs/throttler';
4import helmet from 'helmet';
5
6@Module({
7 imports: [
8 ThrottlerModule.forRoot([
9 { name: 'short', ttl: 1000, limit: 3 },
10 { name: 'long', ttl: 60000, limit: 100 },
11 ]),
12 ],
13})
14export class SecurityModule implements NestModule {
15 configure(consumer: MiddlewareConsumer) {
16 consumer
17 .apply(
18 helmet(),
19 helmet.contentSecurityPolicy({
20 directives: {
21 defaultSrc: ["'self'"],
22 scriptSrc: ["'self'"],
23 },
24 }),
25 )
26 .forRoutes('*');
27 }
28}Security is not a one-time task but a continuous process - just as defending the Empire's walls required constant vigilance from the guards! Each layer of security (SSL, Helmet, CORS, Rate Limiting, CSRF) adds another defensive wall around your application.