This document outlines the comprehensive API security and rate limiting policies implemented in the MarketX backend. The system is designed to protect against abuse, data breaches, and attacks while maintaining a positive experience for legitimate users.
Last Updated: January 22, 2026
Version: 1.0.0
- Rate Limiting
- Security Middleware
- Request Validation
- IP Blocking & Whitelisting
- Security Headers
- Configuration
- Monitoring & Alerts
- Best Practices
- Troubleshooting
Rate limiting is implemented using a custom in-memory throttle guard that tracks requests by client (user ID for authenticated requests, IP address for anonymous requests). Different endpoints have different rate limits based on sensitivity.
- Login: 5 requests per 15 minutes
- Registration: 3 requests per hour
- Password Reset: 3 requests per hour
- 2FA Verification: 10 requests per 15 minutes
Rationale: These endpoints are targets for brute-force attacks and credential stuffing. Strict limits protect user accounts.
- Payment Processing: 10 requests per hour
- Transaction Creation: 20 requests per minute
- Dispute Filing: 5 requests per hour
- Account Closure: 1 request per day
Rationale: These operations involve financial transactions or account changes. Limits prevent accidental or malicious mass operations.
- Create Listing: 20 requests per hour
- Search/Query: 30 requests per 5 minutes
- User Profile Updates: 10 requests per hour
- General API: 100 requests per 15 minutes
Rationale: Normal user operations. Limits prevent resource exhaustion while allowing normal usage.
- File Upload: 10 requests per hour
- Image Processing: 5 requests per minute
- Export/Download: 5 requests per hour
Rationale: File operations are resource-intensive. Limits prevent disk and bandwidth exhaustion.
When a client exceeds the rate limit, the API responds with:
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1642857600
{
"statusCode": 429,
"message": "Too many requests. Please try again in 847 seconds.",
"retryAfter": 847
}All responses include rate limit information in headers:
| Header | Description |
|---|---|
X-RateLimit-Limit |
Total requests allowed in the current window |
X-RateLimit-Remaining |
Requests remaining in current window |
X-RateLimit-Reset |
Unix timestamp when the window resets |
- Rate limits are based on User ID:
user:{userId} - Limits are shared across all devices/sessions for the same user
- Useful for detecting account abuse
- Rate limits are based on IP Address:
ip:{ipAddress} - X-Forwarded-For header is respected (for reverse proxies)
- Falls back to direct socket IP if headers unavailable
Priority Order for IP Detection:
- First IP in
X-Forwarded-Forheader X-Real-IPheader- Direct socket connection IP
All endpoints are automatically rate limited with the API default limit (100/15min). The throttle guard is registered as a global APP_GUARD.
import { RateLimit } from '@/common/decorators/rate-limit.decorator';
@Controller('auth')
export class AuthController {
@Post('login')
@RateLimit('LOGIN') // 5 per 15 minutes
login(@Body() credentials: LoginDto) {
// Implementation
}
@Post('register')
@RateLimit('REGISTER') // 3 per hour
register(@Body() dto: RegisterDto) {
// Implementation
}
@Post('forgot-password')
@RateLimit('PASSWORD_RESET') // 3 per hour
forgotPassword(@Body() dto: ForgotPasswordDto) {
// Implementation
}
// Custom limit
@Post('verify-email')
@RateLimit('CUSTOM', { limit: 5, windowMs: 300000 })
verifyEmail(@Body() dto: VerifyEmailDto) {
// Implementation
}
}For public health checks or admin endpoints that should bypass rate limiting:
import { SkipRateLimit } from '@/common/decorators/rate-limit.decorator';
@Get('health')
@SkipRateLimit()
health() {
return { status: 'ok' };
}The security middleware provides defense-in-depth protection against common web attacks:
- IP blocking/whitelisting
- Request size validation
- Injection attack detection
- Security header injection
- Request sanitization
- Audit logging
Block known malicious IPs or restrict access to whitelisted IPs only.
Configuration:
# Comma-separated list of IPs to block
BLOCKED_IPS=192.0.2.1,192.0.2.2,203.0.113.0
# Comma-separated list of allowed IPs (whitelist mode)
IP_WHITELIST=203.0.113.100,203.0.113.101
# Enable whitelist mode (default: false)
ENABLE_IP_WHITELIST=falseRuntime Management (via Admin API - to be implemented):
// Block an IP
POST /admin/security/ip-blocks
{
"ip": "192.0.2.5",
"reason": "Brute force attack",
"expiresAt": "2026-01-29T00:00:00Z"
}
// Unblock an IP
DELETE /admin/security/ip-blocks/{ip}Prevents denial-of-service attacks via oversized payloads.
Configuration:
MAX_JSON_SIZE=10mb
MAX_URLENCODED_SIZE=10mb
MAX_FILE_SIZE=50mbBehavior:
- Requests exceeding limits receive
400 Bad Request - Content-Type determines which limit applies
- Multipart (file uploads) uses FILE limit
- Regular JSON uses JSON limit
Detects and logs common injection patterns:
- SQL Injection:
' OR '1'='1,DROP TABLE, etc. - XSS:
<script>,javascript:,onerror=, etc. - Path Traversal:
../../../etc/passwd,..%2f%2f - Null Byte Injection:
\x00,%00
Suspicious requests are logged but not blocked by default (applications should implement additional validation).
All responses include security-focused HTTP headers:
| Header | Value | Purpose |
|---|---|---|
X-Content-Type-Options |
nosniff |
Prevents MIME type sniffing |
X-Frame-Options |
DENY |
Prevents clickjacking |
X-XSS-Protection |
1; mode=block |
Enables browser XSS filters |
Strict-Transport-Security |
max-age=31536000 |
Enforces HTTPS |
Content-Security-Policy |
default-src 'self' |
Restricts resource loading |
Referrer-Policy |
strict-origin-when-cross-origin |
Controls referrer information |
Permissions-Policy |
Restricts features | Disables geolocation, microphone, camera |
Customization:
HSTS_MAX_AGE=max-age=31536000; includeSubDomains
CSP_POLICY=default-src 'self'; script-src 'self' trusted-cdn.comPrevents unauthorized cross-origin requests.
Configuration:
# Comma-separated list of allowed origins
CORS_ORIGIN=http://localhost:3000,https://app.marketx.com,https://admin.marketx.comConfigured Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Allowed Headers:
- Content-Type
- Authorization
- X-Requested-With
- X-API-Key
Credentials: Enabled (allows cookies/auth headers)
All security-relevant information is logged:
[SECURITY] POST /api/users | IP: 203.0.113.1 | UserAgent: Mozilla/5.0...
[SUSPICIOUS] Potential attack pattern detected from 192.0.2.1: POST /api/search
[RATE-LIMIT-EXCEEDED] 192.0.2.2 exceeded auth limit (5/15min)
Request → Security Middleware → ValidationPipe → Rate Limiting → Handler
All endpoints benefit from automatic validation:
// In main.ts
app.useGlobalPipes(
new ValidationPipe({
whitelist: true, // Remove unknown properties
forbidNonWhitelisted: true, // Reject unknown properties
transform: true, // Auto-transform types
transformOptions: {
enableImplicitConversion: true,
},
}),
);Use DTOs with class-validator decorators:
import { IsEmail, MinLength, MaxLength } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@MinLength(8)
@MaxLength(128)
password: string;
@MaxLength(100)
username: string;
}
@Post('register')
@RateLimit('REGISTER')
register(@Body() dto: CreateUserDto) {
// dto is validated here
}# Via admin endpoint (implementation required)
curl -X POST http://localhost:3000/admin/security/block-ip \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"ip": "192.0.2.1"}'curl -X DELETE http://localhost:3000/admin/security/block-ip/192.0.2.1 \
-H "Authorization: Bearer $ADMIN_TOKEN"Future versions should implement automatic blocking based on:
- Failed login attempts (>5 in 15 minutes)
- Rate limit violations (>3 consecutive 429 responses)
- Suspicious request patterns (injection attempts, etc.)
- Reports from security systems
Enable whitelist mode for high-security scenarios:
ENABLE_IP_WHITELIST=true
IP_WHITELIST=203.0.113.1,203.0.113.2,203.0.113.0/24Use Cases:
- Admin panel access during emergencies
- Partner API integrations
- High-risk operations (refunds, user deletion)
CSP_POLICY=default-src 'self' 'unsafe-inline' localhost:3000
HSTS_MAX_AGE=max-age=3600CSP_POLICY=default-src 'self'; script-src 'self'; style-src 'self' fonts.googleapis.com; font-src fonts.gstatic.com
HSTS_MAX_AGE=max-age=31536000; includeSubDomains; preload# Check security headers
curl -I https://api.marketx.com/api/status | grep -i "X-\|Strict\|Content-Security\|Referrer"
# Use online tool
# https://securityheaders.com/?q=api.marketx.comCreate .env file or use environment-specific files:
# .env (development)
NODE_ENV=development
RATE_LIMIT_API_LIMIT=100
RATE_LIMIT_API_WINDOW=900000
MAX_JSON_SIZE=10mb
CORS_ORIGIN=http://localhost:3000
BLOCKED_IPS=# .env.production
NODE_ENV=production
RATE_LIMIT_API_LIMIT=50
RATE_LIMIT_API_WINDOW=900000
MAX_JSON_SIZE=5mb
CORS_ORIGIN=https://app.marketx.com
BLOCKED_IPS=192.0.2.0/24,203.0.113.0/24
ENABLE_IP_WHITELIST=true
IP_WHITELIST=203.0.113.100,203.0.113.101Rate limiting configuration is evaluated at startup. To update at runtime:
- Update environment variables on the server
- Restart the application (or implement hot-reload)
- Verify via logs: Check application logs for initialization messages
Example startup logs:
[Bootstrap] Application started on port 3000
[Bootstrap] Environment: production
[Bootstrap] CORS origins: https://app.marketx.com
[Bootstrap] Max JSON payload: 5mb
[Bootstrap] Max file upload: 50mb
[Bootstrap] Rate limiting configured: AUTH=5/15min, API=50/15min
- Rate limit violations: Counter of 429 responses
- Top violators: IPs/users exceeding limits most frequently
- Pattern detection: Unusual request patterns
- Blocked requests: Requests blocked due to suspicious content
- IP blocks: Active IP blocks and their reasons
- Header coverage: Verification that security headers are present
// Critical security events
this.logger.error('Potential DDoS attack detected from IP: 192.0.2.1');
// Security warnings
this.logger.warn('Rate limit exceeded for user: 123 (50/50 requests)');
// Security information
this.logger.log('IP 192.0.2.5 blocked due to brute force attack');
// Debug security details
this.logger.debug('[SECURITY] POST /api/users | IP: 203.0.113.1');Configure centralized logging:
// Example: Winston with ELK stack
import * as winston from 'winston';
const logger = winston.createLogger({
level: process.env.LOG_LEVEL || 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' }),
new elasticsearch.ElasticsearchTransport({
level: 'info',
clientOpts: { node: 'http://localhost:9200' },
index: 'marketx-logs',
}),
],
});Set up alerts for:
- Rate Limit Spike: >50% increase in 429 responses
- Suspicious IPs: >10 failed login attempts from single IP
- Injection Attempts: Any detected SQL injection/XSS pattern
- Large Payloads: Requests near size limits
- Failed CORS: Cross-origin requests from unauthorized origins
-
Always use DTOs with validators
export class CreateProductDto { @IsString() @MaxLength(255) name: string; @IsNumber() @Min(0) price: number; }
-
Apply appropriate rate limits to endpoints
@Post('payment') @RateLimit('PAYMENT') async createPayment(@Body() dto: PaymentDto) { }
-
Handle 429 responses gracefully in clients
if (response.status === 429) { const retryAfter = parseInt(response.headers['retry-after']) * 1000; setTimeout(() => retryRequest(), retryAfter); }
-
Never expose sensitive data in error messages
// Bad throw new BadRequestException(`User with email ${email} already exists`); // Good throw new BadRequestException('Account with this email already exists');
-
Log security events consistently
this.logger.warn(`Failed login attempt for user ${userId} from IP ${ip}`);
-
Monitor security metrics dashboards
- Rate limit violations over time
- Top source IPs
- Error rates by endpoint type
-
Regular security audits
- Review rate limit policies quarterly
- Update blocked IP lists
- Analyze suspicious request patterns
-
Incident response procedures
- Define escalation paths for security alerts
- Maintain playbooks for common attacks
- Track mean time to resolution (MTTR)
-
Keep dependencies updated
# Regularly update security-related packages npm audit npm update @nestjs/common @nestjs/throttler -
Test security policies regularly
# Run security tests npm run test:e2e rate-limiting-security # Manual testing curl -X GET http://localhost:3000/api/status -H "X-Forwarded-For: 192.0.2.1" curl -X GET http://localhost:3000/api/status -H "X-Forwarded-For: 192.0.2.1" curl -X GET http://localhost:3000/api/status -H "X-Forwarded-For: 192.0.2.1" # ... repeat until 429 response
-
Implement exponential backoff
async function requestWithBackoff(url, options = {}) { let retries = 0; while (retries < 5) { try { const response = await fetch(url, options); if (response.status === 429) { const waitTime = (response.headers.get('retry-after') || 60) * 1000; await sleep(waitTime * Math.pow(2, retries)); retries++; continue; } return response; } catch (error) { retries++; } } }
-
Cache responses when appropriate
- Reduce API calls for static data
- Implement local caching strategies
- Use ETag headers for conditional requests
-
Batch requests when possible
// Bad: 100 individual requests for (const id of ids) { await fetchUser(id); } // Good: Batch requests await fetchUsers(ids);
-
Handle rate limit gracefully
- Show user-friendly message
- Disable retry buttons temporarily
- Display Retry-After countdown
Cause: Incorrect IP configuration in reverse proxy
Solution:
# Verify X-Forwarded-For header is correctly passed
curl -v http://localhost:3000/api/test | grep X-Forwarded-For
# Check proxy configuration (nginx example):
# proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
# proxy_set_header X-Real-IP $remote_addr;Cause: Rate limit guard not registered or endpoint has @SkipRateLimit
Solution:
// Verify in app.module.ts:
providers: [
{
provide: APP_GUARD,
useClass: ThrottleGuard, // Should be present
},
],
// Check for @SkipRateLimit decorator
@Get('test')
@SkipRateLimit() // Remove if rate limiting is desired
test() { }Cause: Origin not in CORS_ORIGIN list
Solution:
# Update .env
CORS_ORIGIN=https://yourfrontend.com,http://localhost:3000
# Verify
curl -X OPTIONS http://localhost:3000/api/test \
-H "Origin: https://yourfrontend.com" \
-v | grep Access-ControlCause: CSP policy too restrictive or missing resources
Solution:
# Update CSP to allow legitimate resources
CSP_POLICY=default-src 'self'; script-src 'self' trusted-cdn.com; style-src 'self' fonts.googleapis.comCause: Too many clients in memory, cleanup not running
Solution:
// Check cleanup interval in throttle.guard.ts
setInterval(() => this.cleanupExpiredRecords(), 5 * 60 * 1000); // Every 5 minutes
// Monitor memory usage
process.memoryUsage(); // Should remain stableEnable detailed logging:
# Set environment
NODE_ENV=development
LOG_LEVEL=debug
# Check logs for security middleware output
grep -i "SECURITY\|SUSPICIOUS\|RATE-LIMIT" app.logFor high-traffic deployments:
# Reduce cleanup interval
# In throttle.guard.ts: setInterval(..., 1 * 60 * 1000); // Every 1 minute
# Or use Redis for distributed rate limiting (future enhancement):
# providers: [
# {
# provide: APP_GUARD,
# useClass: RedisThrottleGuard,
# },
# ],- Redis-based rate limiting for distributed systems
- Automated IP blocking based on threat patterns
- Machine learning anomaly detection for suspicious patterns
- Geographic-based rate limits (stricter for high-risk regions)
- Device fingerprinting for better abuse detection
- Custom rate limit policies per user tier (premium vs free)
- Admin dashboard for real-time security monitoring
- Integration with external threat intelligence (AbuseIPDB, etc.)
- Kubernetes: Use horizontal pod autoscaling with shared rate limit store
- Serverless: Implement distributed rate limiting with external store
- Multi-region: Centralize rate limit data in global cache
- Zero-downtime deployment: Maintain rate limit state during updates
For questions or issues related to API security:
- Check this documentation
- Review the code comments in
src/common/ - Check application logs for security warnings
- Contact: security@marketx.com
Document Version: 1.0.0
Last Updated: January 22, 2026
Next Review Date: April 22, 2026