Implement secure API design patterns including authentication, authorization, input validation, rate limiting, and protection against common API vulnerabilities
Add this skill
npx mdskills install sickn33/api-security-best-practicesComprehensive API security guide with detailed code examples and validation patterns
1---2name: api-security-best-practices3description: "Implement secure API design patterns including authentication, authorization, input validation, rate limiting, and protection against common API vulnerabilities"4---56# API Security Best Practices78## Overview910Guide developers in building secure APIs by implementing authentication, authorization, input validation, rate limiting, and protection against common vulnerabilities. This skill covers security patterns for REST, GraphQL, and WebSocket APIs.1112## When to Use This Skill1314- Use when designing new API endpoints15- Use when securing existing APIs16- Use when implementing authentication and authorization17- Use when protecting against API attacks (injection, DDoS, etc.)18- Use when conducting API security reviews19- Use when preparing for security audits20- Use when implementing rate limiting and throttling21- Use when handling sensitive data in APIs2223## How It Works2425### Step 1: Authentication & Authorization2627I'll help you implement secure authentication:28- Choose authentication method (JWT, OAuth 2.0, API keys)29- Implement token-based authentication30- Set up role-based access control (RBAC)31- Secure session management32- Implement multi-factor authentication (MFA)3334### Step 2: Input Validation & Sanitization3536Protect against injection attacks:37- Validate all input data38- Sanitize user inputs39- Use parameterized queries40- Implement request schema validation41- Prevent SQL injection, XSS, and command injection4243### Step 3: Rate Limiting & Throttling4445Prevent abuse and DDoS attacks:46- Implement rate limiting per user/IP47- Set up API throttling48- Configure request quotas49- Handle rate limit errors gracefully50- Monitor for suspicious activity5152### Step 4: Data Protection5354Secure sensitive data:55- Encrypt data in transit (HTTPS/TLS)56- Encrypt sensitive data at rest57- Implement proper error handling (no data leaks)58- Sanitize error messages59- Use secure headers6061### Step 5: API Security Testing6263Verify security implementation:64- Test authentication and authorization65- Perform penetration testing66- Check for common vulnerabilities (OWASP API Top 10)67- Validate input handling68- Test rate limiting697071## Examples7273### Example 1: Implementing JWT Authentication7475```markdown76## Secure JWT Authentication Implementation7778### Authentication Flow79801. User logs in with credentials812. Server validates credentials823. Server generates JWT token834. Client stores token securely845. Client sends token with each request856. Server validates token8687### Implementation8889#### 1. Generate Secure JWT Tokens9091\`\`\`javascript92// auth.js93const jwt = require('jsonwebtoken');94const bcrypt = require('bcrypt');9596// Login endpoint97app.post('/api/auth/login', async (req, res) => {98 try {99 const { email, password } = req.body;100101 // Validate input102 if (!email || !password) {103 return res.status(400).json({104 error: 'Email and password are required'105 });106 }107108 // Find user109 const user = await db.user.findUnique({110 where: { email }111 });112113 if (!user) {114 // Don't reveal if user exists115 return res.status(401).json({116 error: 'Invalid credentials'117 });118 }119120 // Verify password121 const validPassword = await bcrypt.compare(122 password,123 user.passwordHash124 );125126 if (!validPassword) {127 return res.status(401).json({128 error: 'Invalid credentials'129 });130 }131132 // Generate JWT token133 const token = jwt.sign(134 {135 userId: user.id,136 email: user.email,137 role: user.role138 },139 process.env.JWT_SECRET,140 {141 expiresIn: '1h',142 issuer: 'your-app',143 audience: 'your-app-users'144 }145 );146147 // Generate refresh token148 const refreshToken = jwt.sign(149 { userId: user.id },150 process.env.JWT_REFRESH_SECRET,151 { expiresIn: '7d' }152 );153154 // Store refresh token in database155 await db.refreshToken.create({156 data: {157 token: refreshToken,158 userId: user.id,159 expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)160 }161 });162163 res.json({164 token,165 refreshToken,166 expiresIn: 3600167 });168169 } catch (error) {170 console.error('Login error:', error);171 res.status(500).json({172 error: 'An error occurred during login'173 });174 }175});176\`\`\`177178#### 2. Verify JWT Tokens (Middleware)179180\`\`\`javascript181// middleware/auth.js182const jwt = require('jsonwebtoken');183184function authenticateToken(req, res, next) {185 // Get token from header186 const authHeader = req.headers['authorization'];187 const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN188189 if (!token) {190 return res.status(401).json({191 error: 'Access token required'192 });193 }194195 // Verify token196 jwt.verify(197 token,198 process.env.JWT_SECRET,199 {200 issuer: 'your-app',201 audience: 'your-app-users'202 },203 (err, user) => {204 if (err) {205 if (err.name === 'TokenExpiredError') {206 return res.status(401).json({207 error: 'Token expired'208 });209 }210 return res.status(403).json({211 error: 'Invalid token'212 });213 }214215 // Attach user to request216 req.user = user;217 next();218 }219 );220}221222module.exports = { authenticateToken };223\`\`\`224225#### 3. Protect Routes226227\`\`\`javascript228const { authenticateToken } = require('./middleware/auth');229230// Protected route231app.get('/api/user/profile', authenticateToken, async (req, res) => {232 try {233 const user = await db.user.findUnique({234 where: { id: req.user.userId },235 select: {236 id: true,237 email: true,238 name: true,239 // Don't return passwordHash240 }241 });242243 res.json(user);244 } catch (error) {245 res.status(500).json({ error: 'Server error' });246 }247});248\`\`\`249250#### 4. Implement Token Refresh251252\`\`\`javascript253app.post('/api/auth/refresh', async (req, res) => {254 const { refreshToken } = req.body;255256 if (!refreshToken) {257 return res.status(401).json({258 error: 'Refresh token required'259 });260 }261262 try {263 // Verify refresh token264 const decoded = jwt.verify(265 refreshToken,266 process.env.JWT_REFRESH_SECRET267 );268269 // Check if refresh token exists in database270 const storedToken = await db.refreshToken.findFirst({271 where: {272 token: refreshToken,273 userId: decoded.userId,274 expiresAt: { gt: new Date() }275 }276 });277278 if (!storedToken) {279 return res.status(403).json({280 error: 'Invalid refresh token'281 });282 }283284 // Generate new access token285 const user = await db.user.findUnique({286 where: { id: decoded.userId }287 });288289 const newToken = jwt.sign(290 {291 userId: user.id,292 email: user.email,293 role: user.role294 },295 process.env.JWT_SECRET,296 { expiresIn: '1h' }297 );298299 res.json({300 token: newToken,301 expiresIn: 3600302 });303304 } catch (error) {305 res.status(403).json({306 error: 'Invalid refresh token'307 });308 }309});310\`\`\`311312### Security Best Practices313314- ✅ Use strong JWT secrets (256-bit minimum)315- ✅ Set short expiration times (1 hour for access tokens)316- ✅ Implement refresh tokens for long-lived sessions317- ✅ Store refresh tokens in database (can be revoked)318- ✅ Use HTTPS only319- ✅ Don't store sensitive data in JWT payload320- ✅ Validate token issuer and audience321- ✅ Implement token blacklisting for logout322```323324325### Example 2: Input Validation and SQL Injection Prevention326327```markdown328## Preventing SQL Injection and Input Validation329330### The Problem331332**❌ Vulnerable Code:**333\`\`\`javascript334// NEVER DO THIS - SQL Injection vulnerability335app.get('/api/users/:id', async (req, res) => {336 const userId = req.params.id;337338 // Dangerous: User input directly in query339 const query = \`SELECT * FROM users WHERE id = '\${userId}'\`;340 const user = await db.query(query);341342 res.json(user);343});344345// Attack example:346// GET /api/users/1' OR '1'='1347// Returns all users!348\`\`\`349350### The Solution351352#### 1. Use Parameterized Queries353354\`\`\`javascript355// ✅ Safe: Parameterized query356app.get('/api/users/:id', async (req, res) => {357 const userId = req.params.id;358359 // Validate input first360 if (!userId || !/^\d+$/.test(userId)) {361 return res.status(400).json({362 error: 'Invalid user ID'363 });364 }365366 // Use parameterized query367 const user = await db.query(368 'SELECT id, email, name FROM users WHERE id = $1',369 [userId]370 );371372 if (!user) {373 return res.status(404).json({374 error: 'User not found'375 });376 }377378 res.json(user);379});380\`\`\`381382#### 2. Use ORM with Proper Escaping383384\`\`\`javascript385// ✅ Safe: Using Prisma ORM386app.get('/api/users/:id', async (req, res) => {387 const userId = parseInt(req.params.id);388389 if (isNaN(userId)) {390 return res.status(400).json({391 error: 'Invalid user ID'392 });393 }394395 const user = await prisma.user.findUnique({396 where: { id: userId },397 select: {398 id: true,399 email: true,400 name: true,401 // Don't select sensitive fields402 }403 });404405 if (!user) {406 return res.status(404).json({407 error: 'User not found'408 });409 }410411 res.json(user);412});413\`\`\`414415#### 3. Implement Request Validation with Zod416417\`\`\`javascript418const { z } = require('zod');419420// Define validation schema421const createUserSchema = z.object({422 email: z.string().email('Invalid email format'),423 password: z.string()424 .min(8, 'Password must be at least 8 characters')425 .regex(/[A-Z]/, 'Password must contain uppercase letter')426 .regex(/[a-z]/, 'Password must contain lowercase letter')427 .regex(/[0-9]/, 'Password must contain number'),428 name: z.string()429 .min(2, 'Name must be at least 2 characters')430 .max(100, 'Name too long'),431 age: z.number()432 .int('Age must be an integer')433 .min(18, 'Must be 18 or older')434 .max(120, 'Invalid age')435 .optional()436});437438// Validation middleware439function validateRequest(schema) {440 return (req, res, next) => {441 try {442 schema.parse(req.body);443 next();444 } catch (error) {445 res.status(400).json({446 error: 'Validation failed',447 details: error.errors448 });449 }450 };451}452453// Use validation454app.post('/api/users',455 validateRequest(createUserSchema),456 async (req, res) => {457 // Input is validated at this point458 const { email, password, name, age } = req.body;459460 // Hash password461 const passwordHash = await bcrypt.hash(password, 10);462463 // Create user464 const user = await prisma.user.create({465 data: {466 email,467 passwordHash,468 name,469 age470 }471 });472473 // Don't return password hash474 const { passwordHash: _, ...userWithoutPassword } = user;475 res.status(201).json(userWithoutPassword);476 }477);478\`\`\`479480#### 4. Sanitize Output to Prevent XSS481482\`\`\`javascript483const DOMPurify = require('isomorphic-dompurify');484485app.post('/api/comments', authenticateToken, async (req, res) => {486 const { content } = req.body;487488 // Validate489 if (!content || content.length > 1000) {490 return res.status(400).json({491 error: 'Invalid comment content'492 });493 }494495 // Sanitize HTML to prevent XSS496 const sanitizedContent = DOMPurify.sanitize(content, {497 ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],498 ALLOWED_ATTR: ['href']499 });500501 const comment = await prisma.comment.create({502 data: {503 content: sanitizedContent,504 userId: req.user.userId505 }506 });507508 res.status(201).json(comment);509});510\`\`\`511512### Validation Checklist513514- [ ] Validate all user inputs515- [ ] Use parameterized queries or ORM516- [ ] Validate data types (string, number, email, etc.)517- [ ] Validate data ranges (min/max length, value ranges)518- [ ] Sanitize HTML content519- [ ] Escape special characters520- [ ] Validate file uploads (type, size, content)521- [ ] Use allowlists, not blocklists522```523524525### Example 3: Rate Limiting and DDoS Protection526527```markdown528## Implementing Rate Limiting529530### Why Rate Limiting?531532- Prevent brute force attacks533- Protect against DDoS534- Prevent API abuse535- Ensure fair usage536- Reduce server costs537538### Implementation with Express Rate Limit539540\`\`\`javascript541const rateLimit = require('express-rate-limit');542const RedisStore = require('rate-limit-redis');543const Redis = require('ioredis');544545// Create Redis client546const redis = new Redis({547 host: process.env.REDIS_HOST,548 port: process.env.REDIS_PORT549});550551// General API rate limit552const apiLimiter = rateLimit({553 store: new RedisStore({554 client: redis,555 prefix: 'rl:api:'556 }),557 windowMs: 15 * 60 * 1000, // 15 minutes558 max: 100, // 100 requests per window559 message: {560 error: 'Too many requests, please try again later',561 retryAfter: 900 // seconds562 },563 standardHeaders: true, // Return rate limit info in headers564 legacyHeaders: false,565 // Custom key generator (by user ID or IP)566 keyGenerator: (req) => {567 return req.user?.userId || req.ip;568 }569});570571// Strict rate limit for authentication endpoints572const authLimiter = rateLimit({573 store: new RedisStore({574 client: redis,575 prefix: 'rl:auth:'576 }),577 windowMs: 15 * 60 * 1000, // 15 minutes578 max: 5, // Only 5 login attempts per 15 minutes579 skipSuccessfulRequests: true, // Don't count successful logins580 message: {581 error: 'Too many login attempts, please try again later',582 retryAfter: 900583 }584});585586// Apply rate limiters587app.use('/api/', apiLimiter);588app.use('/api/auth/login', authLimiter);589app.use('/api/auth/register', authLimiter);590591// Custom rate limiter for expensive operations592const expensiveLimiter = rateLimit({593 windowMs: 60 * 60 * 1000, // 1 hour594 max: 10, // 10 requests per hour595 message: {596 error: 'Rate limit exceeded for this operation'597 }598});599600app.post('/api/reports/generate',601 authenticateToken,602 expensiveLimiter,603 async (req, res) => {604 // Expensive operation605 }606);607\`\`\`608609### Advanced: Per-User Rate Limiting610611\`\`\`javascript612// Different limits based on user tier613function createTieredRateLimiter() {614 const limits = {615 free: { windowMs: 60 * 60 * 1000, max: 100 },616 pro: { windowMs: 60 * 60 * 1000, max: 1000 },617 enterprise: { windowMs: 60 * 60 * 1000, max: 10000 }618 };619620 return async (req, res, next) => {621 const user = req.user;622 const tier = user?.tier || 'free';623 const limit = limits[tier];624625 const key = \`rl:user:\${user.userId}\`;626 const current = await redis.incr(key);627628 if (current === 1) {629 await redis.expire(key, limit.windowMs / 1000);630 }631632 if (current > limit.max) {633 return res.status(429).json({634 error: 'Rate limit exceeded',635 limit: limit.max,636 remaining: 0,637 reset: await redis.ttl(key)638 });639 }640641 // Set rate limit headers642 res.set({643 'X-RateLimit-Limit': limit.max,644 'X-RateLimit-Remaining': limit.max - current,645 'X-RateLimit-Reset': await redis.ttl(key)646 });647648 next();649 };650}651652app.use('/api/', authenticateToken, createTieredRateLimiter());653\`\`\`654655### DDoS Protection with Helmet656657\`\`\`javascript658const helmet = require('helmet');659660app.use(helmet({661 // Content Security Policy662 contentSecurityPolicy: {663 directives: {664 defaultSrc: ["'self'"],665 styleSrc: ["'self'", "'unsafe-inline'"],666 scriptSrc: ["'self'"],667 imgSrc: ["'self'", 'data:', 'https:']668 }669 },670 // Prevent clickjacking671 frameguard: { action: 'deny' },672 // Hide X-Powered-By header673 hidePoweredBy: true,674 // Prevent MIME type sniffing675 noSniff: true,676 // Enable HSTS677 hsts: {678 maxAge: 31536000,679 includeSubDomains: true,680 preload: true681 }682}));683\`\`\`684685### Rate Limit Response Headers686687\`\`\`688X-RateLimit-Limit: 100689X-RateLimit-Remaining: 87690X-RateLimit-Reset: 1640000000691Retry-After: 900692\`\`\`693```694695## Best Practices696697### ✅ Do This698699- **Use HTTPS Everywhere** - Never send sensitive data over HTTP700- **Implement Authentication** - Require authentication for protected endpoints701- **Validate All Inputs** - Never trust user input702- **Use Parameterized Queries** - Prevent SQL injection703- **Implement Rate Limiting** - Protect against brute force and DDoS704- **Hash Passwords** - Use bcrypt with salt rounds >= 10705- **Use Short-Lived Tokens** - JWT access tokens should expire quickly706- **Implement CORS Properly** - Only allow trusted origins707- **Log Security Events** - Monitor for suspicious activity708- **Keep Dependencies Updated** - Regularly update packages709- **Use Security Headers** - Implement Helmet.js710- **Sanitize Error Messages** - Don't leak sensitive information711712### ❌ Don't Do This713714- **Don't Store Passwords in Plain Text** - Always hash passwords715- **Don't Use Weak Secrets** - Use strong, random JWT secrets716- **Don't Trust User Input** - Always validate and sanitize717- **Don't Expose Stack Traces** - Hide error details in production718- **Don't Use String Concatenation for SQL** - Use parameterized queries719- **Don't Store Sensitive Data in JWT** - JWTs are not encrypted720- **Don't Ignore Security Updates** - Update dependencies regularly721- **Don't Use Default Credentials** - Change all default passwords722- **Don't Disable CORS Completely** - Configure it properly instead723- **Don't Log Sensitive Data** - Sanitize logs724725## Common Pitfalls726727### Problem: JWT Secret Exposed in Code728**Symptoms:** JWT secret hardcoded or committed to Git729**Solution:**730\`\`\`javascript731// ❌ Bad732const JWT_SECRET = 'my-secret-key';733734// ✅ Good735const JWT_SECRET = process.env.JWT_SECRET;736if (!JWT_SECRET) {737 throw new Error('JWT_SECRET environment variable is required');738}739740// Generate strong secret741// node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"742\`\`\`743744### Problem: Weak Password Requirements745**Symptoms:** Users can set weak passwords like "password123"746**Solution:**747\`\`\`javascript748const passwordSchema = z.string()749 .min(12, 'Password must be at least 12 characters')750 .regex(/[A-Z]/, 'Must contain uppercase letter')751 .regex(/[a-z]/, 'Must contain lowercase letter')752 .regex(/[0-9]/, 'Must contain number')753 .regex(/[^A-Za-z0-9]/, 'Must contain special character');754755// Or use a password strength library756const zxcvbn = require('zxcvbn');757const result = zxcvbn(password);758if (result.score < 3) {759 return res.status(400).json({760 error: 'Password too weak',761 suggestions: result.feedback.suggestions762 });763}764\`\`\`765766### Problem: Missing Authorization Checks767**Symptoms:** Users can access resources they shouldn't768**Solution:**769\`\`\`javascript770// ❌ Bad: Only checks authentication771app.delete('/api/posts/:id', authenticateToken, async (req, res) => {772 await prisma.post.delete({ where: { id: req.params.id } });773 res.json({ success: true });774});775776// ✅ Good: Checks both authentication and authorization777app.delete('/api/posts/:id', authenticateToken, async (req, res) => {778 const post = await prisma.post.findUnique({779 where: { id: req.params.id }780 });781782 if (!post) {783 return res.status(404).json({ error: 'Post not found' });784 }785786 // Check if user owns the post or is admin787 if (post.userId !== req.user.userId && req.user.role !== 'admin') {788 return res.status(403).json({789 error: 'Not authorized to delete this post'790 });791 }792793 await prisma.post.delete({ where: { id: req.params.id } });794 res.json({ success: true });795});796\`\`\`797798### Problem: Verbose Error Messages799**Symptoms:** Error messages reveal system details800**Solution:**801\`\`\`javascript802// ❌ Bad: Exposes database details803app.post('/api/users', async (req, res) => {804 try {805 const user = await prisma.user.create({ data: req.body });806 res.json(user);807 } catch (error) {808 res.status(500).json({ error: error.message });809 // Error: "Unique constraint failed on the fields: (`email`)"810 }811});812813// ✅ Good: Generic error message814app.post('/api/users', async (req, res) => {815 try {816 const user = await prisma.user.create({ data: req.body });817 res.json(user);818 } catch (error) {819 console.error('User creation error:', error); // Log full error820821 if (error.code === 'P2002') {822 return res.status(400).json({823 error: 'Email already exists'824 });825 }826827 res.status(500).json({828 error: 'An error occurred while creating user'829 });830 }831});832\`\`\`833834## Security Checklist835836### Authentication & Authorization837- [ ] Implement strong authentication (JWT, OAuth 2.0)838- [ ] Use HTTPS for all endpoints839- [ ] Hash passwords with bcrypt (salt rounds >= 10)840- [ ] Implement token expiration841- [ ] Add refresh token mechanism842- [ ] Verify user authorization for each request843- [ ] Implement role-based access control (RBAC)844845### Input Validation846- [ ] Validate all user inputs847- [ ] Use parameterized queries or ORM848- [ ] Sanitize HTML content849- [ ] Validate file uploads850- [ ] Implement request schema validation851- [ ] Use allowlists, not blocklists852853### Rate Limiting & DDoS Protection854- [ ] Implement rate limiting per user/IP855- [ ] Add stricter limits for auth endpoints856- [ ] Use Redis for distributed rate limiting857- [ ] Return proper rate limit headers858- [ ] Implement request throttling859860### Data Protection861- [ ] Use HTTPS/TLS for all traffic862- [ ] Encrypt sensitive data at rest863- [ ] Don't store sensitive data in JWT864- [ ] Sanitize error messages865- [ ] Implement proper CORS configuration866- [ ] Use security headers (Helmet.js)867868### Monitoring & Logging869- [ ] Log security events870- [ ] Monitor for suspicious activity871- [ ] Set up alerts for failed auth attempts872- [ ] Track API usage patterns873- [ ] Don't log sensitive data874875## OWASP API Security Top 108768771. **Broken Object Level Authorization** - Always verify user can access resource8782. **Broken Authentication** - Implement strong authentication mechanisms8793. **Broken Object Property Level Authorization** - Validate which properties user can access8804. **Unrestricted Resource Consumption** - Implement rate limiting and quotas8815. **Broken Function Level Authorization** - Verify user role for each function8826. **Unrestricted Access to Sensitive Business Flows** - Protect critical workflows8837. **Server Side Request Forgery (SSRF)** - Validate and sanitize URLs8848. **Security Misconfiguration** - Use security best practices and headers8859. **Improper Inventory Management** - Document and secure all API endpoints88610. **Unsafe Consumption of APIs** - Validate data from third-party APIs887888## Related Skills889890- `@ethical-hacking-methodology` - Security testing perspective891- `@sql-injection-testing` - Testing for SQL injection892- `@xss-html-injection` - Testing for XSS vulnerabilities893- `@broken-authentication` - Authentication vulnerabilities894- `@backend-dev-guidelines` - Backend development standards895- `@systematic-debugging` - Debug security issues896897## Additional Resources898899- [OWASP API Security Top 10](https://owasp.org/www-project-api-security/)900- [JWT Best Practices](https://tools.ietf.org/html/rfc8725)901- [Express Security Best Practices](https://expressjs.com/en/advanced/best-practice-security.html)902- [Node.js Security Checklist](https://blog.risingstack.com/node-js-security-checklist/)903- [API Security Checklist](https://github.com/shieldfy/API-Security-Checklist)904905---906907**Pro Tip:** Security is not a one-time task - regularly audit your APIs, keep dependencies updated, and stay informed about new vulnerabilities!908
Full transparency — inspect the skill content before installing.