api-security-patterns
This repository contains a complete **Cursor (Claude AI)** infrastructure that provides **auto-activating skills**, **specialized agents**, and **powerful commands** to help you build better applications faster.
- Category: discovered
- Tags: github, community
- Version: 1.0.0
- Author: walid
- Downloads: 0
- Updated: 2025-11-22
Download the latest version (v1.0.0) from this page.
SKILL.md
# API Security Patterns
**Skill Version:** 1.0.0
**Stack:** Next.js API Routes, TypeScript
**Purpose:** Production-ready API security including rate limiting, headers, and request validation
## Overview
This skill provides battle-tested patterns for securing Next.js API routes, including rate limiting, security headers, CORS configuration, input validation, and request sanitization.
## When to Use This Skill
Load this skill when working on:
- API route handlers
- Middleware for security
- Rate limiting implementation
- Security headers configuration
- API request validation
- Protected endpoints
## Quick Reference
### Core Security Layers
1. **Rate Limiting** - Prevent abuse and DOS attacks
2. **Security Headers** - HSTS, X-Frame-Options, CSP, etc.
3. **Input Validation** - Validate and sanitize all inputs
4. **Authentication** - Verify user identity
5. **Authorization** - Check user permissions
6. **Error Handling** - Don't leak sensitive information
## Core Guidelines
### 1. Rate Limiting Implementation
```typescript
// lib/security/rateLimit.ts
interface RateLimitStore {
[key: string]: {
count: number;
resetTime: number;
};
}
const store: RateLimitStore = {};
// Cleanup old entries every 5 minutes
setInterval(() => {
const now = Date.now();
Object.keys(store).forEach(key => {
if (store[key].resetTime < now) {
delete store[key];
}
});
}, 5 * 60 * 1000);
export interface RateLimitConfig {
/**
* Maximum number of requests allowed in the time window
*/
maxRequests: number;
/**
* Time window in seconds
*/
windowSeconds: number;
/**
* Custom identifier function (defaults to IP address)
*/
identifier?: (request: Request) => string;
}
export interface RateLimitResult {
success: boolean;
limit: number;
remaining: number;
reset: number;
}
/**
* Check if request is within rate limit
*/
export function checkRateLimit(
request: Request,
config: RateLimitConfig
): RateLimitResult {
const identifier = config.identifier
? config.identifier(request)
: getClientIdentifier(request);
const now = Date.now();
const windowMs = config.windowSeconds * 1000;
if (!store[identifier] || store[identifier].resetTime < now) {
// First request or window expired
store[identifier] = {
count: 1,
resetTime: now + windowMs,
};
return {
success: true,
limit: config.maxRequests,
remaining: config.maxRequests - 1,
reset: store[identifier].resetTime,
};
}
// Increment count
store[identifier].count++;
const remaining = Math.max(0, config.maxRequests - store[identifier].count);
const success = store[identifier].count <= config.maxRequests;
return {
success,
limit: config.maxRequests,
remaining,
reset: store[identifier].resetTime,
};
}
/**
* Get client identifier (IP address)
*/
function getClientIdentifier(request: Request): string {
const headers = request.headers;
// Check for Cloud Run / Load Balancer forwarded IP
const forwardedFor = headers.get('x-forwarded-for');
if (forwardedFor) {
return forwardedFor.split(',')[0].trim();
}
// Check for other common headers
const realIp = headers.get('x-real-ip');
if (realIp) {
return realIp;
}
// Fallback to a constant (not ideal, but prevents crashes)
return 'unknown';
}
/**
* Predefined rate limit configurations
*/
export const RateLimitPresets = {
// Strict limits for authentication endpoints
AUTH: {
maxRequests: 20,
windowSeconds: 60 * 3, // 3 minutes
},
// Moderate limits for API endpoints
API: {
maxRequests: 100,
windowSeconds: 60, // 1 minute
},
// Lenient limits for general pages
PAGE: {
maxRequests: 200,
windowSeconds: 60, // 1 minute
},
// Very strict for password reset
PASSWORD_RESET: {
maxRequests: 10,
windowSeconds: 60 * 60, // 1 hour
},
// Strict for data mutations
MUTATION: {
maxRequests: 30,
windowSeconds: 60, // 1 minute
},
} as const;
/**
* Create rate limit response
*/
export function createRateLimitResponse(result: RateLimitResult): Response {
return new Response(
JSON.stringify({
error: 'Too many requests',
message: 'You have exceeded the rate limit. Please try again later.',
limit: result.limit,
remaining: result.remaining,
reset: new Date(result.reset).toISOString(),
}),
{
status: 429,
headers: {
'Content-Type': 'application/json',
'X-RateLimit-Limit': result.limit.toString(),
'X-RateLimit-Remaining': result.remaining.toString(),
'X-RateLimit-Reset': result.reset.toString(),
'Retry-After': Math.ceil((result.reset - Date.now()) / 1000).toString(),
},
}
);
}
```
### 2. Security Headers Configuration
```typescript
// lib/security/headers.ts
export interface SecurityHeaders {
'X-DNS-Prefetch-Control': string;
'Strict-Transport-Security': string;
'X-Frame-Options': string;
'X-Content-Type-Options': string;
'Referrer-Policy': string;
'Permissions-Policy': string;
'X-XSS-Protection': string;
}
/**
* Get security headers for responses
*/
export function getSecurityHeaders(): SecurityHeaders {
return {
// Disable DNS prefetching
'X-DNS-Prefetch-Control': 'off',
// Enforce HTTPS (only in production)
'Strict-Transport-Security': 'max-age=63072000; includeSubDomains; preload',
// Prevent clickjacking
'X-Frame-Options': 'DENY',
// Prevent MIME type sniffing
'X-Content-Type-Options': 'nosniff',
// Control referrer information
'Referrer-Policy': 'strict-origin-when-cross-origin',
// Restrict browser features
'Permissions-Policy': 'geolocation=(), microphone=(), camera=()',
// XSS protection (legacy, but still useful)
'X-XSS-Protection': '1; mode=block',
};
}
/**
* Apply security headers to a response
*/
export function applySecurityHeaders(response: Response): Response {
const headers = getSecurityHeaders();
Object.entries(headers).forEach(([key, value]) => {
response.headers.set(key, value);
});
return response;
}
/**
* Get CORS headers for API responses
*/
export function getCorsHeaders(origin?: string): Record<string, string> {
// In production, you should whitelist specific origins
const allowedOrigins = process.env.NEXT_PUBLIC_ALLOWED_ORIGINS?.split(',') || [];
// Check if origin is allowed
const isAllowed = allowedOrigins.includes('*') ||
(origin && allowedOrigins.includes(origin));
if (!isAllowed && allowedOrigins.length > 0) {
return {};
}
return {
'Access-Control-Allow-Origin': origin || '*',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
'Access-Control-Max-Age': '86400', // 24 hours
};
}
/**
* Handle CORS preflight requests
*/
export function handleCorsPrefligh(request: Request): Response {
const origin = request.headers.get('origin') || undefined;
const corsHeaders = getCorsHeaders(origin);
return new Response(null, {
status: 204,
headers: corsHeaders,
});
}
```
### 3. Protected API Route Pattern
```typescript
// app/api/orders/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { checkRateLimit, RateLimitPresets, createRateLimitResponse } from '@/lib/security/rateLimit';
import { applySecurityHeaders } from '@/lib/security/headers';
import { verifyAuth } from '@/lib/auth';
import { sanitizeObject } from '@/lib/validation';
export async function POST(request: NextRequest) {
try {
// 1. Rate Limiting
const rateLimitResult = checkRateLimit(request, RateLimitPresets.MUTATION);
if (!rateLimitResult.success) {
return createRateLimitResponse(rateLimitResult);
}
// 2. Authentication
const userId = await verifyAuth(request);
if (!userId) {
return NextResponse.json(
{ error: 'Unauthorized' },
{ status: 401 }
);
}
// 3. Parse and validate body
let body;
try {
body = await request.json();
} catch {
return NextResponse.json(
{ error: 'Invalid JSON body' },
{ status: 400 }
);
}
// 4. Validate required fields
const { amount, description, date } = body;
if (!amount || !description || !date) {
return NextResponse.json(
{ error: 'Missing required fields' },
{ status: 400 }
);
}
// 5. Validate data types and ranges
const numAmount = parseFloat(amount);
if (isNaN(numAmount) || numAmount <= 0 || numAmount > 1000000) {
return NextResponse.json(
{ error: 'Invalid amount' },
{ status: 400 }
);
}
// 6. Sanitize input
const sanitizedData = sanitizeObject({
amount: numAmount,
description,
date,
userId,
});
// 7. Process the request
// const order = await createOrder(sanitizedData);
// 8. Return success with security headers
const response = NextResponse.json({
success: true,
data: { id: 'order-123' },
});
return applySecurityHeaders(response);
} catch (error) {
console.error('Error processing order:', error);
// Don't leak error details to client
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
}
// Handle OPTIONS for CORS
export async function OPTIONS(request: NextRequest) {
return handleCorsPrefligh(request);
}
```
### 4. Request Validation Middleware
```typescript
// lib/api/validation.ts
import { NextRequest, NextResponse } from 'next/server';
import { z, ZodSchema } from 'zod';
/**
* Validate request body against a Zod schema
*/
export async function validateRequest<T>(
request: NextRequest,
schema: ZodSchema<T>
): Promise<{ success: true; data: T } | { success: false; response: NextResponse }> {
try {
const body = await request.json();
const validatedData = schema.parse(body);
return { success: true, data: validatedData };
} catch (error) {
if (error instanceof z.ZodError) {
return {
success: false,
response: NextResponse.json(
{
error: 'Validation failed',
details: error.errors.map(err => ({
field: err.path.join('.'),
message: err.message,
})),
},
{ status: 400 }
),
};
}
return {
success: false,
response: NextResponse.json(
{ error: 'Invalid request body' },
{ status: 400 }
),
};
}
}
/**
* Check request body size
*/
export function validateBodySize(
body: any,
maxSizeKb: number = 100
): boolean {
const bodyString = JSON.stringify(body);
const sizeKb = new Blob([bodyString]).size / 1024;
return sizeKb <= maxSizeKb;
}
```
### 5. API Error Handler Wrapper
```typescript
// lib/api/error-handler.ts
import { NextRequest, NextResponse } from 'next/server';
export class APIError extends Error {
constructor(
message: string,
public statusCode: number = 500,
public code?: string
) {
super(message);
this.name = 'APIError';
}
}
type Handler = (
request: NextRequest,
context?: any
) => Promise<NextResponse>;
/**
* Wrap API route handler with error handling
*/
export function withErrorHandler(handler: Handler): Handler {
return async (request: NextRequest, context?: any) => {
try {
return await handler(request, context);
} catch (error) {
console.error('API Error:', error);
if (error instanceof APIError) {
return NextResponse.json(
{
error: error.message,
code: error.code,
},
{ status: error.statusCode }
);
}
// Don't leak internal error details
return NextResponse.json(
{ error: 'Internal server error' },
{ status: 500 }
);
}
};
}
/**
* Usage example
*/
// export const POST = withErrorHandler(async (request) => {
// throw new APIError('Not found', 404, 'RESOURCE_NOT_FOUND');
// });
```
### 6. Complete Secure API Route Example
```typescript
// app/api/products/route.ts
import { NextRequest, NextResponse } from 'next/server';
import { z } from 'zod';
import {
checkRateLimit,
RateLimitPresets,
createRateLimitResponse
} from '@/lib/security/rateLimit';
import { applySecurityHeaders } from '@/lib/security/headers';
import { validateRequest } from '@/lib/api/validation';
import { withErrorHandler, APIError } from '@/lib/api/error-handler';
import { verifyAuth } from '@/lib/auth';
import { sanitizeObject } from '@/lib/validation';
// Define validation schema
const createProductSchema = z.object({
sku: z.string().regex(/^PRD-\d{4,}$/),
price: z.number().min(0.01).max(1000000),
name: z.string().min(2).max(100),
description: z.string().max(500),
releaseDate: z.string().datetime(),
});
export const POST = withErrorHandler(async (request: NextRequest) => {
// 1. Rate Limiting
const rateLimitResult = checkRateLimit(request, RateLimitPresets.MUTATION);
if (!rateLimitResult.success) {
return createRateLimitResponse(rateLimitResult);
}
// 2. Authentication
const userId = await verifyAuth(request);
if (!userId) {
throw new APIError('Unauthorized', 401, 'AUTH_REQUIRED');
}
// 3. Validate request body
const validation = await validateRequest(request, createProductSchema);
if (!validation.success) {
return validation.response;
}
const { data } = validation;
// 4. Additional business validation
// Check if SKU already exists
// const existingProduct = await checkProductExists(data.sku);
// if (existingProduct) {
// throw new APIError('SKU already exists', 409, 'DUPLICATE_SKU');
// }
// 5. Sanitize data (extra safety)
const sanitizedData = sanitizeObject({
...data,
userId,
createdAt: new Date().toISOString(),
});
// 6. Process request
// const product = await createProduct(sanitizedData);
// 7. Return success with security headers
const response = NextResponse.json({
success: true,
data: {
id: 'prd-123',
...sanitizedData,
},
}, { status: 201 });
// Add rate limit headers
response.headers.set('X-RateLimit-Limit', rateLimitResult.limit.toString());
response.headers.set('X-RateLimit-Remaining', rateLimitResult.remaining.toString());
return applySecurityHeaders(response);
});
// GET endpoint with pagination and filtering
export const GET = withErrorHandler(async (request: NextRequest) => {
// Rate limiting
const rateLimitResult = checkRateLimit(request, RateLimitPresets.API);
if (!rateLimitResult.success) {
return createRateLimitResponse(rateLimitResult);
}
// Authentication
const userId = await verifyAuth(request);
if (!userId) {
throw new APIError('Unauthorized', 401, 'AUTH_REQUIRED');
}
// Parse query parameters
const { searchParams } = new URL(request.url);
const page = Math.max(1, parseInt(searchParams.get('page') || '1'));
const limit = Math.min(100, Math.max(1, parseInt(searchParams.get('limit') || '20')));
const category = searchParams.get('category') || undefined;
// Fetch data
// const products = await getProducts({ userId, page, limit, category });
const response = NextResponse.json({
data: [], // products,
pagination: {
page,
limit,
total: 0,
},
});
return applySecurityHeaders(response);
});
```
### 7. Middleware for Global Security
```typescript
// middleware.ts (root level)
import { NextRequest, NextResponse } from 'next/server';
import { applySecurityHeaders } from '@/lib/security/headers';
export function middleware(request: NextRequest) {
// Apply security headers to all responses
const response = NextResponse.next();
return applySecurityHeaders(response);
}
export const config = {
matcher: [
/*
* Match all request paths except:
* - _next/static (static files)
* - _next/image (image optimization files)
* - favicon.ico (favicon file)
*/
'/((?!_next/static|_next/image|favicon.ico).*)',
],
};
```
## Security Best Practices
### Do's ✅
- **Always rate limit** sensitive endpoints (auth, mutations)
- **Validate all inputs** on both client and server
- **Use TypeScript** for type safety
- **Sanitize user input** before storing
- **Apply security headers** to all responses
- **Log security events** (rate limit hits, auth failures)
- **Use environment variables** for sensitive config
- **Implement CORS** properly for cross-origin requests
- **Return generic errors** to users (don't leak details)
- **Use HTTPS** in production
### Don'ts ❌
- **Don't trust client data** - always validate server-side
- **Don't expose stack traces** to users
- **Don't use weak rate limits** on critical endpoints
- **Don't skip authentication** checks
- **Don't log sensitive data** (passwords, tokens)
- **Don't allow unlimited request sizes**
- **Don't use `any` types** - be explicit
- **Don't forget OPTIONS** handler for CORS
- **Don't hard-code secrets** in code
- **Don't ignore error handling**
## Rate Limit Guidelines
| Endpoint Type | Suggested Limit | Window |
|--------------|----------------|--------|
| Authentication | 20 requests | 3 minutes |
| Password Reset | 10 requests | 1 hour |
| Data Mutations | 30 requests | 1 minute |
| Read Operations | 100 requests | 1 minute |
| Public Pages | 200 requests | 1 minute |
| File Uploads | 10 requests | 5 minutes |
## Security Checklist
- [ ] Rate limiting implemented
- [ ] Security headers configured
- [ ] Input validation on all endpoints
- [ ] Authentication required for protected routes
- [ ] Authorization checks in place
- [ ] Request body size limits
- [ ] CORS properly configured
- [ ] Error handling doesn't leak info
- [ ] Logging for security events
- [ ] HTTPS enforced in production
---
**Last Updated:** 2025-01-10
**Maintained By:** Development Team