examples
API Security Examples
Section titled “API Security Examples”Example 1: Implementing JWT Authentication
Section titled “Example 1: Implementing JWT Authentication”Secure JWT Authentication Implementation
Section titled “Secure JWT Authentication Implementation”Authentication Flow
Section titled “Authentication Flow”- User logs in with credentials
- Server validates credentials
- Server generates JWT token
- Client stores token securely
- Client sends token with each request
- Server validates token
Implementation
Section titled “Implementation”1. Generate Secure JWT Tokens
const jwt = require('jsonwebtoken');const bcrypt = require('bcrypt');
// Login endpointapp.post('/api/auth/login', async (req, res) => { try { const { email, password } = req.body;
// Validate input if (!email || !password) { return res.status(400).json({ error: 'Email and password are required' }); }
// Find user const user = await db.user.findUnique({ where: { email } });
if (!user) { // Don't reveal if user exists return res.status(401).json({ error: 'Invalid credentials' }); }
// Verify password const validPassword = await bcrypt.compare( password, user.passwordHash );
if (!validPassword) { return res.status(401).json({ error: 'Invalid credentials' }); }
// Generate JWT token const token = jwt.sign( { userId: user.id, email: user.email, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1h', issuer: 'your-app', audience: 'your-app-users' } );
// Generate refresh token const refreshToken = jwt.sign( { userId: user.id }, process.env.JWT_REFRESH_SECRET, { expiresIn: '7d' } );
// Store refresh token in database await db.refreshToken.create({ data: { token: refreshToken, userId: user.id, expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000) } });
res.json({ token, refreshToken, expiresIn: 3600 });
} catch (error) { console.error('Login error:', error); res.status(500).json({ error: 'An error occurred during login' }); }});2. Verify JWT Tokens (Middleware)
const jwt = require('jsonwebtoken');
function authenticateToken(req, res, next) { // Get token from header const authHeader = req.headers['authorization']; const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) { return res.status(401).json({ error: 'Access token required' }); }
// Verify token jwt.verify( token, process.env.JWT_SECRET, { issuer: 'your-app', audience: 'your-app-users' }, (err, user) => { if (err) { if (err.name === 'TokenExpiredError') { return res.status(401).json({ error: 'Token expired' }); } return res.status(403).json({ error: 'Invalid token' }); }
// Attach user to request req.user = user; next(); } );}
module.exports = { authenticateToken };3. Protect Routes
const { authenticateToken } = require('./middleware/auth');
// Protected routeapp.get('/api/user/profile', authenticateToken, async (req, res) => { try { const user = await db.user.findUnique({ where: { id: req.user.userId }, select: { id: true, email: true, name: true, // Don't return passwordHash } });
res.json(user); } catch (error) { res.status(500).json({ error: 'Server error' }); }});4. Implement Token Refresh
app.post('/api/auth/refresh', async (req, res) => { const { refreshToken } = req.body;
if (!refreshToken) { return res.status(401).json({ error: 'Refresh token required' }); }
try { // Verify refresh token const decoded = jwt.verify( refreshToken, process.env.JWT_REFRESH_SECRET );
// Check if refresh token exists in database const storedToken = await db.refreshToken.findFirst({ where: { token: refreshToken, userId: decoded.userId, expiresAt: { gt: new Date() } } });
if (!storedToken) { return res.status(403).json({ error: 'Invalid refresh token' }); }
// Generate new access token const user = await db.user.findUnique({ where: { id: decoded.userId } });
const newToken = jwt.sign( { userId: user.id, email: user.email, role: user.role }, process.env.JWT_SECRET, { expiresIn: '1h' } );
res.json({ token: newToken, expiresIn: 3600 });
} catch (error) { res.status(403).json({ error: 'Invalid refresh token' }); }});Security Best Practices
Section titled “Security Best Practices”- ✅ Use strong JWT secrets (256-bit minimum)
- ✅ Set short expiration times (1 hour for access tokens)
- ✅ Implement refresh tokens for long-lived sessions
- ✅ Store refresh tokens in database (can be revoked)
- ✅ Use HTTPS only
- ✅ Don’t store sensitive data in JWT payload
- ✅ Validate token issuer and audience
- ✅ Implement token blacklisting for logout
Example 2: Input Validation and SQL Injection Prevention
Section titled “Example 2: Input Validation and SQL Injection Prevention”Preventing SQL Injection and Input Validation
Section titled “Preventing SQL Injection and Input Validation”The Problem
Section titled “The Problem”❌ Vulnerable Code:
// NEVER DO THIS - SQL Injection vulnerabilityapp.get('/api/users/:id', async (req, res) => { const userId = req.params.id;
// Dangerous: User input directly in query const query = `SELECT * FROM users WHERE id = '${userId}'`; const user = await db.query(query);
res.json(user);});
// Attack example:// GET /api/users/1' OR '1'='1// Returns all users!The Solution
Section titled “The Solution”1. Use Parameterized Queries
// ✅ Safe: Parameterized queryapp.get('/api/users/:id', async (req, res) => { const userId = req.params.id;
// Validate input first if (!userId || /^\d+$/.test(userId)) { return res.status(400).json({ error: 'Invalid user ID' }); }
// Use parameterized query const user = await db.query( 'SELECT id, email, name FROM users WHERE id = $1', [userId] );
if (!user) { return res.status(404).json({ error: 'User not found' }); }
res.json(user);});2. Use ORM with Proper Escaping
// ✅ Safe: Using Prisma ORMapp.get('/api/users/:id', async (req, res) => { const userId = parseInt(req.params.id);
if (isNaN(userId)) { return res.status(400).json({ error: 'Invalid user ID' }); }
const user = await prisma.user.findUnique({ where: { id: userId }, select: { id: true, email: true, name: true, // Don't select sensitive fields } });
if (!user) { return res.status(404).json({ error: 'User not found' }); }
res.json(user);});3. Implement Request Validation with Zod
const { z } = require('zod');
// Define validation schemaconst createUserSchema = z.object({ email: z.string().email('Invalid email format'), password: z.string() .min(8, 'Password must be at least 8 characters') .regex(/[A-Z]/, 'Password must contain uppercase letter') .regex(/[a-z]/, 'Password must contain lowercase letter') .regex(/[0-9]/, 'Password must contain number'), name: z.string() .min(2, 'Name must be at least 2 characters') .max(100, 'Name too long'), age: z.number() .int('Age must be an integer') .min(18, 'Must be 18 or older') .max(120, 'Invalid age') .optional()});
// Validation middlewarefunction validateRequest(schema) { return (req, res, next) => { try { schema.parse(req.body); next(); } catch (error) { res.status(400).json({ error: 'Validation failed', details: error.errors }); } };}
// Use validationapp.post('/api/users', validateRequest(createUserSchema), async (req, res) => { // Input is validated at this point const { email, password, name, age } = req.body;
// Hash password const passwordHash = await bcrypt.hash(password, 10);
// Create user const user = await prisma.user.create({ data: { email, passwordHash, name, age } });
// Don't return password hash const { passwordHash: _, ...userWithoutPassword } = user; res.status(201).json(userWithoutPassword); });4. Sanitize Output to Prevent XSS
const DOMPurify = require('isomorphic-dompurify');
app.post('/api/comments', authenticateToken, async (req, res) => { const { content } = req.body;
// Validate if (!content || content.length > 1000) { return res.status(400).json({ error: 'Invalid comment content' }); }
// Sanitize HTML to prevent XSS const sanitizedContent = DOMPurify.sanitize(content, { ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'], ALLOWED_ATTR: ['href'] });
const comment = await prisma.comment.create({ data: { content: sanitizedContent, userId: req.user.userId } });
res.status(201).json(comment);});Validation Checklist
Section titled “Validation Checklist”- Validate all user inputs
- Use parameterized queries or ORM
- Validate data types (string, number, email, etc.)
- Validate data ranges (min/max length, value ranges)
- Sanitize HTML content
- Escape special characters
- Validate file uploads (type, size, content)
- Use allowlists, not blocklists
Example 3: Rate Limiting and DDoS Protection
Section titled “Example 3: Rate Limiting and DDoS Protection”Implementing Rate Limiting
Section titled “Implementing Rate Limiting”Why Rate Limiting?
Section titled “Why Rate Limiting?”- Prevent brute force attacks
- Protect against DDoS
- Prevent API abuse
- Ensure fair usage
- Reduce server costs
Implementation with Express Rate Limit
Section titled “Implementation with Express Rate Limit”const rateLimit = require('express-rate-limit');const RedisStore = require('rate-limit-redis');const Redis = require('ioredis');
// Create Redis clientconst redis = new Redis({ host: process.env.REDIS_HOST, port: process.env.REDIS_PORT});
// General API rate limitconst apiLimiter = rateLimit({ store: new RedisStore({ client: redis, prefix: 'rl:api:' }), windowMs: 15 * 60 * 1000, // 15 minutes max: 100, // 100 requests per window message: { error: 'Too many requests, please try again later', retryAfter: 900 // seconds }, standardHeaders: true, // Return rate limit info in headers legacyHeaders: false, // Custom key generator (by user ID or IP) keyGenerator: (req) => { return req.user?.userId || req.ip; }});
// Strict rate limit for authentication endpointsconst authLimiter = rateLimit({ store: new RedisStore({ client: redis, prefix: 'rl:auth:' }), windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // Only 5 login attempts per 15 minutes skipSuccessfulRequests: true, // Don't count successful logins message: { error: 'Too many login attempts, please try again later', retryAfter: 900 }});
// Apply rate limitersapp.use('/api/', apiLimiter);app.use('/api/auth/login', authLimiter);app.use('/api/auth/register', authLimiter);
// Custom rate limiter for expensive operationsconst expensiveLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 10, // 10 requests per hour message: { error: 'Rate limit exceeded for this operation' }});
app.post('/api/reports/generate', authenticateToken, expensiveLimiter, async (req, res) => { // Expensive operation });Advanced: Per-User Rate Limiting
Section titled “Advanced: Per-User Rate Limiting”// Different limits based on user tierfunction createTieredRateLimiter() { const limits = { free: { windowMs: 60 * 60 * 1000, max: 100 }, pro: { windowMs: 60 * 60 * 1000, max: 1000 }, enterprise: { windowMs: 60 * 60 * 1000, max: 10000 } };
return async (req, res, next) => { const user = req.user; const tier = user?.tier || 'free'; const limit = limits[tier];
const key = `rl:user:${user.userId}`; const current = await redis.incr(key);
if (current === 1) { await redis.expire(key, limit.windowMs / 1000); }
if (current > limit.max) { return res.status(429).json({ error: 'Rate limit exceeded', limit: limit.max, remaining: 0, reset: await redis.ttl(key) }); }
// Set rate limit headers res.set({ 'X-RateLimit-Limit': limit.max, 'X-RateLimit-Remaining': limit.max - current, 'X-RateLimit-Reset': await redis.ttl(key) });
next(); };}
app.use('/api/', authenticateToken, createTieredRateLimiter());DDoS Protection with Helmet
Section titled “DDoS Protection with Helmet”const helmet = require('helmet');
app.use(helmet({ // Content Security Policy contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'"], scriptSrc: ["'self'"], imgSrc: ["'self'", 'data:', 'https:'] } }, // Prevent clickjacking frameguard: { action: 'deny' }, // Hide X-Powered-By header hidePoweredBy: true, // Prevent MIME type sniffing noSniff: true, // Enable HSTS hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }}));Rate Limit Response Headers
Section titled “Rate Limit Response Headers”X-RateLimit-Limit: 100X-RateLimit-Remaining: 87X-RateLimit-Reset: 1640000000Retry-After: 900