Security Checklist for NestJS Backend Applications
A practical 30-point security checklist for NestJS backend applications, covering authentication, authorization, input validation, rate limiting, secrets, CORS, CSRF, file uploads, logging, dependencies, and infrastructure.

Building an API with NestJS is relatively straightforward. Building an API that remains secure when exposed to the internet, handling untrusted input, authentication, file uploads, external integrations, and sensitive information requires additional care.
NestJS provides several mechanisms for implementing security, including Guards, Pipes, interceptors, authentication, authorization, rate limiting, and security headers. However, the framework cannot decide which business rules need to be protected in your specific application.
This checklist brings together practical security checks that can be performed before deploying a NestJS application to production.
1. Is authentication implemented correctly?
Every application with users or private data needs to clearly define how the identity of the client will be validated.
Check that:
- Private endpoints require authentication.
- Invalid tokens are rejected.
- Expired tokens are rejected.
- The JWT validation algorithm is explicitly configured.
- The application does not accept unexpected algorithms.
- Keys and secrets are not hardcoded.
- Refresh tokens are protected against improper reuse.
- Tokens have an expiration time appropriate to the application’s risk level.
NestJS provides Guards to protect routes and also allows an authentication Guard to be registered globally, leaving only explicitly marked routes public.
A useful strategy is:
By default, a route should be private. Public access should be an explicit exception.
2. Authentication is not authorization
An authenticated user is not automatically allowed to access every resource.
Consider an API such as:
GET /users/123/ordersThe fact that the user is authenticated does not mean they are allowed to view the orders belonging to user 123.
This type of issue is related to Broken Object Level Authorization (BOLA), listed as API1 in the OWASP API Security Top 10.
Check that:
- Each resource verifies who is allowed to access it.
- IDs sent by the client are not treated as authorization.
- A user cannot access another user’s resources simply by changing an ID.
- Administrative operations require additional authorization.
- Update, delete, export, and similar operations require the appropriate permissions.
In NestJS, Guards can be used to structure mechanisms such as RBAC, but resource-level authorization still needs to reflect the actual business rules of the application.
3. Do not trust data provided by the client
Never consider data safe simply because there is a DTO or because TypeScript defines a type.
The client controls the request.
It can send:
{
"name": "Fernando",
"role": "admin",
"isActive": true,
"balance": 999999
}If the application blindly persists everything it receives, this can lead to mass assignment or authorization issues at the property level.
The API should explicitly define which fields can be accepted for each operation.
For example, prefer specific DTOs:
export class CreateUserDto {
name: string;
email: string;
}Instead of directly using a database entity as an API input model.
4. Use ValidationPipe globally
Input validation should happen before request data reaches your business logic.
A common starting configuration is:
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);Check that:
ValidationPipeis configured globally.- Specific DTOs are used for input.
- Required fields are explicitly validated.
- Enums are validated.
- Maximum lengths are defined.
- Strings have appropriate validation rules.
- IDs are validated.
- Pagination values are limited.
- Unexpected properties are rejected or removed.
The whitelist option can remove properties that do not have validation decorators, while forbidNonWhitelisted can reject requests containing unexpected properties.
5. Never rely on TypeScript as a security mechanism
This:
createUser(dto: CreateUserDto)does not prevent an HTTP request from sending arbitrary JSON.
TypeScript provides compile-time assistance, but HTTP input arrives from outside the application and must be validated at runtime.
In other words:
TypeScript ≠ runtime input validationValidation must happen at the boundary between your application and the outside world.
6. Configure CORS explicitly
CORS should not simply be treated as:
app.enableCors();without considering the application’s architecture.
Explicitly define which origins are allowed to consume your API.
For example:
app.enableCors({
origin: [
'https://app.example.com',
],
credentials: true,
});Avoid allowing arbitrary origins in environments that use cookie-based authentication.
Also remember:
CORS does not replace authentication or authorization.
It controls how browsers enforce cross-origin access. It does not prevent another HTTP client from directly calling your API.
7. Protect cookie-based applications against CSRF
If authentication relies on cookies that browsers send automatically, evaluate whether the application requires protection against Cross-Site Request Forgery (CSRF).
Also review authentication cookies for attributes such as:
HttpOnly
Secure
SameSiteThe correct strategy depends on how the frontend, backend, and authentication domains are structured.
An API using:
Authorization: Bearer <token>has a different threat model from an application relying on authenticated sessions stored in cookies.
8. Rate-limit login and sensitive operations
Endpoints such as:
POST /auth/login
POST /auth/forgot-password
POST /auth/reset-password
POST /auth/verify-code
POST /auth/send-codeshould not accept unlimited attempts.
NestJS provides @nestjs/throttler for implementing rate limiting and protecting against attacks such as brute force.
For example:
ThrottlerModule.forRoot({
throttlers: [
{
ttl: 60_000,
limit: 10,
},
],
});But do not necessarily use the same policy for every endpoint.
A request such as:
GET /productsmay require a completely different limit from:
POST /auth/loginFor distributed applications, also make sure the rate-limiting storage strategy is compatible with multiple application instances.
9. Never store passwords in plain text
Passwords should never be stored as:
password123and they should not be stored using reversible encryption.
Use a proper password hashing algorithm.
NestJS documents approaches using libraries such as bcrypt and argon2.
Example:
const hash = await bcrypt.hash(password, 12);And to verify:
const valid = await bcrypt.compare(password, hash);Never implement your own password hashing algorithm.
10. Secrets should never be committed to Git
Search your repository for values such as:
JWT_SECRET=
DATABASE_PASSWORD=
AWS_SECRET_ACCESS_KEY=
API_KEY=
PRIVATE_KEY=Secrets should not be stored in source code.
Use an appropriate secret-management strategy for each environment:
Development → local .env
Staging → secret manager / environment
Production → secret manager / environmentAlso review:
.gitignore
Docker images
CI/CD logs
build artifacts
backups
logsRemoving a secret from the current source file does not necessarily fix a leak that already exists in Git history.
11. Validate environment variables
A common production problem is starting an application with an incomplete configuration:
DATABASE_URL=
JWT_SECRET=
REDIS_HOST=and discovering the problem only when a specific endpoint is used.
Validate required environment variables during application startup.
The application should fail fast when a required configuration value is missing or invalid.
That is much safer than allowing the application to start in a partially configured state.
12. Use HTTPS
Never treat HTTPS as something only the frontend is responsible for.
All communication involving:
- credentials;
- tokens;
- personal data;
- financial information;
- administrative data;
should use secure transport.
In production, also review:
HTTP → HTTPS
HSTS
TLS configuration
Secure cookies
Proxy configurationIf a reverse proxy is being used, verify that the application correctly handles the original scheme and client IP when those values affect authentication, logging, or rate limiting.
13. Configure security headers
HTTP applications can use security headers to enable browser-side protections.
For current NestJS versions, app.useSecurityHeaders() can be used to apply security headers through the underlying security-header implementation. Helmet is also documented as an alternative.
Example:
const app = await NestFactory.create(AppModule);
app.useSecurityHeaders();For versions that do not provide this API, Helmet can be used directly:
import helmet from 'helmet';
app.use(helmet());Always test the resulting configuration with Swagger, GraphQL IDEs, embedded resources, and other components that may be affected by Content Security Policy or other headers.
14. Do not expose sensitive information in error messages
Avoid responses such as:
{
"error": "SQLSTATE[HY000]: Access denied for user..."
}or:
{
"error": "/var/www/app/src/modules/users/users.service.ts:42"
}Production error responses should provide enough information for the client without exposing:
- stack traces;
- internal paths;
- credentials;
- SQL queries;
- internal service names;
- infrastructure details.
Detailed diagnostic information should remain available to internal observability systems rather than being returned to end users.
15. Never log secrets
Pay special attention to:
Authorization
Cookie
password
refreshToken
accessToken
apiKey
clientSecretAvoid:
logger.debug(request.body);
logger.debug(request.headers);unless you fully understand the contents being logged.
Logs are valuable for incident investigation, but they must be treated as potentially sensitive data.
16. Do not trust frontend validation alone
This:
if (password.length < 8) {
// ...
}in React, Vue, or Angular does not protect the API.
An attacker can completely bypass the user interface and send:
curlor use their own HTTP client.
Every relevant security rule must be enforced on the backend.
17. Protect against SQL and NoSQL injection
Never build queries by directly concatenating user-controlled values.
Avoid:
const query = `
SELECT *
FROM users
WHERE email = '${email}'
`;Prefer parameterized queries and the safe query mechanisms provided by your ORM or database driver.
The same concern applies to:
- SQL;
- MongoDB;
- Redis;
- search engines;
- dynamic filters;
- external commands.
Using an ORM can reduce certain classes of risk, but it does not automatically make every query safe.
18. Limit pagination and result sizes
An endpoint such as:
GET /usersshould not allow:
GET /users?limit=999999999Define explicit limits:
page >= 1
limit >= 1
limit <= 100This reduces risks related to:
- excessive memory consumption;
- expensive queries;
- huge responses;
- automated abuse.
This is also related to Unrestricted Resource Consumption, identified as API4 in the OWASP API Security Top 10.
19. Be careful with file uploads
File uploads are an attack surface by themselves.
Do not trust only the extension:
image.jpgCheck, as appropriate:
- maximum size;
- MIME type;
- allowed extensions;
- actual file content when necessary;
- number of uploaded files;
- file name;
- storage location;
- antivirus or sandboxing requirements.
Never store uploaded files in a directory where the web server can execute them.
20. Protect download endpoints
Hiding a file link is not authorization.
Avoid assuming that:
GET /files/123is safe simply because the ID exists.
Verify:
authenticated user
resource ownership
required permission
resource availabilityA file download endpoint needs authorization just like any other API resource.
21. Protect external integrations against SSRF
Be especially careful with endpoints such as:
POST /fetch-urlreceiving:
{
"url": "https://example.com"
}If the backend makes arbitrary HTTP requests to user-provided URLs, the application may become vulnerable to Server-Side Request Forgery (SSRF).
When possible:
- use host allowlists;
- block internal networks;
- block localhost;
- validate redirects;
- enforce connection timeouts;
- limit response sizes;
- disable unnecessary protocols.
SSRF is included as API7 in the OWASP API Security Top 10.
22. Validate incoming webhooks
Integrations such as:
Stripe
GitHub
Mercado Pago
PagSeguro
internal systems
authentication providersoften send webhooks.
Do not rely only on the source IP.
Whenever the provider offers it, validate:
signature
timestamp
payload
idempotencyAlso store event identifiers so the same event cannot be processed multiple times.
23. Implement idempotency where necessary
Operations such as:
create payment
issue document
create order
send charge
process webhookmay receive the same request more than once.
A secure application should not turn a retry into multiple financial or administrative operations.
When appropriate, use an idempotency key:
Idempotency-Key: 9c3d...and ensure that the operation is processed only once.
24. Restrict administrative functions
Endpoints such as:
POST /admin/users
DELETE /admin/users/:id
GET /admin/audit
POST /admin/settingsdeserve an additional authorization layer.
Do not scatter checks such as:
if (user.role === 'admin')throughout the application.
Centralize authorization rules using Guards, policies, or another consistent mechanism.
NestJS provides examples for RBAC and other authorization strategies.
25. Do not expose unnecessary internal endpoints
Review the API for routes such as:
/debug
/test
/internal
/admin
/metrics
/swagger
/api-jsonNot every endpoint created during development needs to be publicly available.
Swagger deserves special attention because it can expose both the API interface and the OpenAPI document.
In production, consider whether the documentation should be:
protected by authentication
available only internally
disabledThe important thing is to make the decision consciously.
26. Maintain an endpoint inventory
A large API may contain:
/users
/users/:id
/orders
/orders/:id
/admin
/internal
/webhooks
/debugand each endpoint may have a different security policy.
Perform regular reviews of the exposed API surface.
For each endpoint, document:
Method
URL
Authentication
Authorization
Input data
Response data
Rate limit
SensitivityThis helps identify forgotten, obsolete, or inconsistently protected endpoints.
The OWASP API Security Top 10 also highlights Improper Inventory Management as API9.
27. Return only the fields that are necessary
Imagine:
return user;and the entity contains:
id
name
email
passwordHash
refreshToken
resetToken
internalNotesEven if serialization happens automatically, sensitive information may accidentally be exposed.
Prefer response DTOs:
export class UserResponseDto {
id: string;
name: string;
email: string;
}The API response should be an explicit decision.
28. Keep dependencies updated and audited
A NestJS application is not made up only of the code you wrote.
It depends on components such as:
NestJS
Express/Fastify
ORM
JWT libraries
bcrypt/argon2
Swagger
Redis
RabbitMQ
helper librariesInclude dependency checks in CI/CD:
npm auditand keep dependencies updated in a controlled way.
Also monitor vulnerabilities in transitive dependencies.
29. Automate security testing
Before deployment, test at least:
401 → unauthenticated endpoint
403 → unauthorized user
404 → nonexistent resource
400 → invalid payload
429 → too many requests
413 → oversized payloadAlso test scenarios such as:
user A accessing user B's resource
regular user accessing an admin endpoint
expired token
invalid token
refresh token reuse
manipulated IDs
unexpected fields in payloads
invalid uploads
malicious external URLsThe goal is not only to prove that the happy path works.
The goal is to prove that forbidden paths are actually blocked.
30. Review infrastructure together with the code
A secure NestJS application can still be exposed because of infrastructure mistakes.
Before deploying to production, review:
HTTPS
Firewall
Reverse proxy
Database
Redis
Queues
Docker
Volumes
Secrets
Backups
SSH
Logs
Monitoring
AlertsAvoid exposing services such as:
PostgreSQL
MySQL
Redis
RabbitMQ
MongoDBdirectly to the internet unless there is a specific reason.
Ideally, the public surface should remain small:
Internet
↓
Reverse Proxy / Load Balancer
↓
NestJS
↓
Internal ServicesA main.ts starting point
A basic setup can centralize several of these protections:
import { NestFactory } from '@nestjs/core';
import { ValidationPipe } from '@nestjs/common';
import { AppModule } from './app.module';
async function bootstrap() {
const app = await NestFactory.create(AppModule);
app.setGlobalPrefix('api');
app.enableCors({
origin: ['https://app.example.com'],
credentials: true,
});
app.useGlobalPipes(
new ValidationPipe({
whitelist: true,
forbidNonWhitelisted: true,
transform: true,
}),
);
app.useSecurityHeaders();
await app.listen(process.env.PORT ?? 3000);
}
bootstrap();The exact configuration depends on the NestJS version, HTTP adapter, authentication strategy, and application architecture.
For versions where useSecurityHeaders() is not available, Helmet remains a documented alternative.
Final pre-deployment checklist
Use this as a final review:
[ ] HTTPS enforced
[ ] Authentication implemented
[ ] Authorization implemented
[ ] Guards protect private endpoints
[ ] DTOs defined
[ ] Global ValidationPipe configured
[ ] whitelist enabled
[ ] Mass assignment reviewed
[ ] CORS explicitly configured
[ ] CSRF evaluated
[ ] Rate limiting configured
[ ] Brute force protection implemented
[ ] Passwords hashed with bcrypt/Argon2
[ ] JWT expiration configured
[ ] Secrets kept out of Git
[ ] Environment variables validated
[ ] Error responses contain no sensitive information
[ ] Logs contain no tokens or passwords
[ ] SQL/NoSQL injection reviewed
[ ] Pagination limits enforced
[ ] Uploads restricted
[ ] Downloads authorized
[ ] SSRF reviewed
[ ] Webhook signatures validated
[ ] Critical operations are idempotent
[ ] Administrative endpoints protected
[ ] Swagger/API documentation protected
[ ] Unused/internal endpoints removed
[ ] Dependencies audited
[ ] Automated security tests implemented
[ ] Infrastructure reviewedSecurity is not a middleware
One of the biggest mistakes when working with NestJS is believing that installing Helmet, enabling CORS, and configuring JWT means the application is secure.
These tools are important, but they represent only part of the problem.
API security exists across multiple layers:
Internet
│
▼
HTTPS / Proxy
│
▼
Rate Limiting
│
▼
Authentication
│
▼
Authorization
│
▼
Validation
│
▼
Business Rules
│
▼
DatabaseEach layer needs to assume that the previous one may be insufficient.
An authenticated request may still be unauthorized.
An authorized request may still contain malicious or invalid data.
Valid input can still abuse a resource.
And a properly protected application can still leak secrets through logs or infrastructure configuration.
That is why security should be treated as a continuous process rather than as a single middleware configuration.
The best time to run this checklist is not after an incident.
It is before the first production deployment.
References
- NestJS — Authentication
- NestJS — Authorization
- NestJS — Validation
- NestJS — Rate Limiting
- NestJS — Security Headers / Helmet
- NestJS — CORS
- NestJS — CSRF Protection
- NestJS — Encryption and Hashing
- NestJS — File Upload
- OWASP API Security Top 10
- OWASP Secrets Management Cheat Sheet
- OWASP Logging Cheat Sheet