Security Overview
Security in a NestJS application is not a single feature you switch on — it is a set of overlapping defenses applied at the transport, HTTP, application, and data layers. Because Nest sits on top of Express or Fastify, you inherit the wider Node.js ecosystem of middleware and guards, and Nest gives you first-class building blocks (guards, interceptors, pipes, middleware) to wire them in cleanly. This page surveys the OWASP-aligned threats every API faces and maps each one to the Nest mechanism that defends against it.
The layers Nest gives you
Nest’s request lifecycle is the backbone of its security model. Each incoming request flows through middleware, guards, interceptors, and pipes before it ever reaches your handler — and each stage is a natural place to enforce a control.
| Layer | Nest primitive | Typical security use |
|---|---|---|
| Transport | reverse proxy / TLS | Force HTTPS, terminate TLS |
| HTTP headers | Middleware (Helmet) | CSP, HSTS, frame options |
| Cross-origin | app.enableCors() | Restrict allowed origins |
| Authorization | Guards (CanActivate) | AuthN/AuthZ, RBAC |
| Throttling | ThrottlerGuard | Brute-force / DoS mitigation |
| Input | Pipes (ValidationPipe) | Schema validation, sanitization |
Wiring these globally in main.ts gives you a consistent baseline:
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import helmet from 'helmet';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
// 1. Secure HTTP headers
app.use(helmet());
// 2. Lock CORS to known front-ends
app.enableCors({
origin: ['https://app.devcraftly.com'],
methods: ['GET', 'POST', 'PUT', 'PATCH', 'DELETE'],
credentials: true,
});
// 3. Validate and strip every payload
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
await app.listen(3000);
}
bootstrap();
Mapping OWASP risks to Nest defenses
The OWASP API Security Top 10 names the failures attackers exploit most. Nest does not magically solve them, but it gives you the right place to put each control.
| OWASP risk | Where it bites | Nest defense |
|---|---|---|
| Broken authentication | Stolen/forged tokens | Passport guards, JWT strategy |
| Broken object-level authorization | Accessing others’ records | Custom guards checking ownership |
| Excessive data exposure | Returning whole entities | Serialization interceptor + @Exclude() |
| Lack of rate limiting | Brute force, credential stuffing | @nestjs/throttler |
| Injection | Unsanitized input | ValidationPipe, parameterized queries (TypeORM/Prisma) |
| Security misconfiguration | Missing headers | Helmet, disabled x-powered-by |
Authorization with guards
Guards are the idiomatic place for authentication and authorization. They run before the handler and short-circuit with a 403/401 when access is denied.
import {
CanActivate,
ExecutionContext,
Injectable,
ForbiddenException,
} from '@nestjs/common';
@Injectable()
export class OwnerGuard implements CanActivate {
canActivate(ctx: ExecutionContext): boolean {
const req = ctx.switchToHttp().getRequest();
if (req.user?.id !== req.params.userId) {
throw new ForbiddenException('You do not own this resource');
}
return true;
}
}
Hiding sensitive fields
A ClassSerializerInterceptor paired with class-transformer decorators prevents over-exposure of entity data such as password hashes.
import { Exclude } from 'class-transformer';
export class UserEntity {
id: string;
email: string;
@Exclude()
passwordHash: string;
}
Output:
GET /users/42
{
"id": "42",
"email": "[email protected]"
}
The passwordHash field never leaves the server.
CSRF and state-changing requests
CSRF only matters when the browser auto-attaches credentials — i.e. cookie-based sessions. Pure bearer-token APIs (the Authorization header is not sent automatically by browsers) are largely immune. If you use cookies, add the csrf-csrf (or csurf legacy) middleware and pair it with SameSite=Lax cookies.
import { doubleCsrf } from 'csrf-csrf';
const { doubleCsrfProtection } = doubleCsrf({
getSecret: () => process.env.CSRF_SECRET!,
cookieName: '__Host-csrf',
});
app.use(doubleCsrfProtection);
Tip: Choose ONE auth transport per surface. Mixing cookies and bearer tokens on the same endpoint multiplies your attack surface and makes CSRF reasoning much harder.
A practical security checklist
Treat this as a pre-deploy gate for every Nest service:
- Transport — Terminate TLS, redirect HTTP to HTTPS, and emit HSTS via Helmet.
- Headers — Enable Helmet with a deliberate Content-Security-Policy; remove
x-powered-by. - CORS — Allowlist exact origins; never reflect arbitrary
Originwith credentials enabled. - Input — Apply a global
ValidationPipewithwhitelistandforbidNonWhitelisted; use DTOs everywhere. - Rate limiting — Throttle auth and write endpoints with
@nestjs/throttler. - AuthZ — Enforce both authentication and per-object authorization in guards.
- Secrets — Load from environment/secret manager via
@nestjs/config; never commit them.
Warning: A global
ValidationPipeonly validates inputs decorated on DTO classes. Endpoints that accept rawreq.bodybypass it entirely — always bind a typed DTO.
Best Practices
- Apply security globally in
main.tsso new modules inherit the baseline automatically rather than opting in. - Prefer Nest primitives (guards, pipes, interceptors) over scattered inline checks — they are testable and reusable.
- Validate with
whitelist: trueto silently strip unknown properties and block mass-assignment attacks. - Use a parameterized ORM (Prisma, TypeORM) and never concatenate user input into queries.
- Set restrictive CORS and CSP policies, then loosen deliberately — default-deny beats default-allow.
- Keep dependencies patched and run
npm auditin CI; most real-world breaches exploit known CVEs. - Log security-relevant events (failed logins, throttle hits) without logging secrets or full tokens.