Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
Guides implementation of secure REST, GraphQL, and WebSocket APIs covering auth, input validation, rate limiting, and OWASP Top 10.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
SKILL.md
1---2name: api-security-best-practices3description: "Implement secure API design patterns including authentication, authorization, input validation, rate limiting, and protection against common API vulnerabilities"4risk: unknown5source: community6date_added: "2026-02-27"7---89# API Security Best Practices1011## Overview1213Guide 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.1415## When to Use This Skill1617- Use when designing new API endpoints18- Use when securing existing APIs19- Use when implementing authentication and authorization20- Use when protecting against API attacks (injection, DDoS, etc.)21- Use when conducting API security reviews22- Use when preparing for security audits23- Use when implementing rate limiting and throttling24- Use when handling sensitive data in APIs2526## How It Works2728### Step 1: Authentication & Authorization2930I'll help you implement secure authentication:31- Choose authentication method (JWT, OAuth 2.0, API keys)32- Implement token-based authentication33- Set up role-based access control (RBAC)34- Secure session management35- Implement multi-factor authentication (MFA)3637### Step 2: Input Validation & Sanitization3839Protect against injection attacks:40- Validate all input data41- Sanitize user inputs42- Use parameterized queries43- Implement request schema validation44- Prevent SQL injection, XSS, and command injection4546### Step 3: Rate Limiting & Throttling4748Prevent abuse and DDoS attacks:49- Implement rate limiting per user/IP50- Set up API throttling51- Configure request quotas52- Handle rate limit errors gracefully53- Monitor for suspicious activity5455### Step 4: Data Protection5657Secure sensitive data:58- Encrypt data in transit (HTTPS/TLS)59- Encrypt sensitive data at rest60- Implement proper error handling (no data leaks)61- Sanitize error messages62- Use secure headers6364### Step 5: API Security Testing6566Verify security implementation:67- Test authentication and authorization68- Perform penetration testing69- Check for common vulnerabilities (OWASP API Top 10)70- Validate input handling71- Test rate limiting727374## Examples7576### Example 1: Implementing JWT Authentication7778```markdown79## Secure JWT Authentication Implementation8081### Authentication Flow82831. User logs in with credentials842. Server validates credentials853. Server generates JWT token864. Client stores token securely875. Client sends token with each request886. Server validates token8990### Implementation9192#### 1. Generate Secure JWT Tokens9394\`\`\`javascript95// auth.js96const jwt = require('jsonwebtoken');97const bcrypt = require('bcrypt');9899// Login endpoint100app.post('/api/auth/login', async (req, res) => {101try {102const { email, password } = req.body;103104// Validate input105if (!email || !password) {106return res.status(400).json({107error: 'Email and password are required'108});109}110111// Find user112const user = await db.user.findUnique({113where: { email }114});115116if (!user) {117// Don't reveal if user exists118return res.status(401).json({119error: 'Invalid credentials'120});121}122123// Verify password124const validPassword = await bcrypt.compare(125password,126user.passwordHash127);128129if (!validPassword) {130return res.status(401).json({131error: 'Invalid credentials'132});133}134135// Generate JWT token136const token = jwt.sign(137{138userId: user.id,139email: user.email,140role: user.role141},142process.env.JWT_SECRET,143{144expiresIn: '1h',145issuer: 'your-app',146audience: 'your-app-users'147}148);149150// Generate refresh token151const refreshToken = jwt.sign(152{ userId: user.id },153process.env.JWT_REFRESH_SECRET,154{ expiresIn: '7d' }155);156157// Store refresh token in database158await db.refreshToken.create({159data: {160token: refreshToken,161userId: user.id,162expiresAt: new Date(Date.now() + 7 * 24 * 60 * 60 * 1000)163}164});165166res.json({167token,168refreshToken,169expiresIn: 3600170});171172} catch (error) {173console.error('Login error:', error);174res.status(500).json({175error: 'An error occurred during login'176});177}178});179\`\`\`180181#### 2. Verify JWT Tokens (Middleware)182183\`\`\`javascript184// middleware/auth.js185const jwt = require('jsonwebtoken');186187function authenticateToken(req, res, next) {188// Get token from header189const authHeader = req.headers['authorization'];190const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN191192if (!token) {193return res.status(401).json({194error: 'Access token required'195});196}197198// Verify token199jwt.verify(200token,201process.env.JWT_SECRET,202{203issuer: 'your-app',204audience: 'your-app-users'205},206(err, user) => {207if (err) {208if (err.name === 'TokenExpiredError') {209return res.status(401).json({210error: 'Token expired'211});212}213return res.status(403).json({214error: 'Invalid token'215});216}217218// Attach user to request219req.user = user;220next();221}222);223}224225module.exports = { authenticateToken };226\`\`\`227228#### 3. Protect Routes229230\`\`\`javascript231const { authenticateToken } = require('./middleware/auth');232233// Protected route234app.get('/api/user/profile', authenticateToken, async (req, res) => {235try {236const user = await db.user.findUnique({237where: { id: req.user.userId },238select: {239id: true,240email: true,241name: true,242// Don't return passwordHash243}244});245246res.json(user);247} catch (error) {248res.status(500).json({ error: 'Server error' });249}250});251\`\`\`252253#### 4. Implement Token Refresh254255\`\`\`javascript256app.post('/api/auth/refresh', async (req, res) => {257const { refreshToken } = req.body;258259if (!refreshToken) {260return res.status(401).json({261error: 'Refresh token required'262});263}264265try {266// Verify refresh token267const decoded = jwt.verify(268refreshToken,269process.env.JWT_REFRESH_SECRET270);271272// Check if refresh token exists in database273const storedToken = await db.refreshToken.findFirst({274where: {275token: refreshToken,276userId: decoded.userId,277expiresAt: { gt: new Date() }278}279});280281if (!storedToken) {282return res.status(403).json({283error: 'Invalid refresh token'284});285}286287// Generate new access token288const user = await db.user.findUnique({289where: { id: decoded.userId }290});291292const newToken = jwt.sign(293{294userId: user.id,295email: user.email,296role: user.role297},298process.env.JWT_SECRET,299{ expiresIn: '1h' }300);301302res.json({303token: newToken,304expiresIn: 3600305});306307} catch (error) {308res.status(403).json({309error: 'Invalid refresh token'310});311}312});313\`\`\`314315### Security Best Practices316317- ✅ Use strong JWT secrets (256-bit minimum)318- ✅ Set short expiration times (1 hour for access tokens)319- ✅ Implement refresh tokens for long-lived sessions320- ✅ Store refresh tokens in database (can be revoked)321- ✅ Use HTTPS only322- ✅ Don't store sensitive data in JWT payload323- ✅ Validate token issuer and audience324- ✅ Implement token blacklisting for logout325```326327328### Example 2: Input Validation and SQL Injection Prevention329330```markdown331## Preventing SQL Injection and Input Validation332333### The Problem334335**❌ Vulnerable Code:**336\`\`\`javascript337// NEVER DO THIS - SQL Injection vulnerability338app.get('/api/users/:id', async (req, res) => {339const userId = req.params.id;340341// Dangerous: User input directly in query342const query = \`SELECT * FROM users WHERE id = '\${userId}'\`;343const user = await db.query(query);344345res.json(user);346});347348// Attack example:349// GET /api/users/1' OR '1'='1350// Returns all users!351\`\`\`352353### The Solution354355#### 1. Use Parameterized Queries356357\`\`\`javascript358// ✅ Safe: Parameterized query359app.get('/api/users/:id', async (req, res) => {360const userId = req.params.id;361362// Validate input first363if (!userId || !/^\d+$/.test(userId)) {364return res.status(400).json({365error: 'Invalid user ID'366});367}368369// Use parameterized query370const user = await db.query(371'SELECT id, email, name FROM users WHERE id = $1',372[userId]373);374375if (!user) {376return res.status(404).json({377error: 'User not found'378});379}380381res.json(user);382});383\`\`\`384385#### 2. Use ORM with Proper Escaping386387\`\`\`javascript388// ✅ Safe: Using Prisma ORM389app.get('/api/users/:id', async (req, res) => {390const userId = parseInt(req.params.id);391392if (isNaN(userId)) {393return res.status(400).json({394error: 'Invalid user ID'395});396}397398const user = await prisma.user.findUnique({399where: { id: userId },400select: {401id: true,402email: true,403name: true,404// Don't select sensitive fields405}406});407408if (!user) {409return res.status(404).json({410error: 'User not found'411});412}413414res.json(user);415});416\`\`\`417418#### 3. Implement Request Validation with Zod419420\`\`\`javascript421const { z } = require('zod');422423// Define validation schema424const createUserSchema = z.object({425email: z.string().email('Invalid email format'),426password: z.string()427.min(8, 'Password must be at least 8 characters')428.regex(/[A-Z]/, 'Password must contain uppercase letter')429.regex(/[a-z]/, 'Password must contain lowercase letter')430.regex(/[0-9]/, 'Password must contain number'),431name: z.string()432.min(2, 'Name must be at least 2 characters')433.max(100, 'Name too long'),434age: z.number()435.int('Age must be an integer')436.min(18, 'Must be 18 or older')437.max(120, 'Invalid age')438.optional()439});440441// Validation middleware442function validateRequest(schema) {443return (req, res, next) => {444try {445schema.parse(req.body);446next();447} catch (error) {448res.status(400).json({449error: 'Validation failed',450details: error.errors451});452}453};454}455456// Use validation457app.post('/api/users',458validateRequest(createUserSchema),459async (req, res) => {460// Input is validated at this point461const { email, password, name, age } = req.body;462463// Hash password464const passwordHash = await bcrypt.hash(password, 10);465466// Create user467const user = await prisma.user.create({468data: {469email,470passwordHash,471name,472age473}474});475476// Don't return password hash477const { passwordHash: _, ...userWithoutPassword } = user;478res.status(201).json(userWithoutPassword);479}480);481\`\`\`482483#### 4. Sanitize Output to Prevent XSS484485\`\`\`javascript486const DOMPurify = require('isomorphic-dompurify');487488app.post('/api/comments', authenticateToken, async (req, res) => {489const { content } = req.body;490491// Validate492if (!content || content.length > 1000) {493return res.status(400).json({494error: 'Invalid comment content'495});496}497498// Sanitize HTML to prevent XSS499const sanitizedContent = DOMPurify.sanitize(content, {500ALLOWED_TAGS: ['b', 'i', 'em', 'strong', 'a'],501ALLOWED_ATTR: ['href']502});503504const comment = await prisma.comment.create({505data: {506content: sanitizedContent,507userId: req.user.userId508}509});510511res.status(201).json(comment);512});513\`\`\`514515### Validation Checklist516517- [ ] Validate all user inputs518- [ ] Use parameterized queries or ORM519- [ ] Validate data types (string, number, email, etc.)520- [ ] Validate data ranges (min/max length, value ranges)521- [ ] Sanitize HTML content522- [ ] Escape special characters523- [ ] Validate file uploads (type, size, content)524- [ ] Use allowlists, not blocklists525```526527528### Example 3: Rate Limiting and DDoS Protection529530```markdown531## Implementing Rate Limiting532533### Why Rate Limiting?534535- Prevent brute force attacks536- Protect against DDoS537- Prevent API abuse538- Ensure fair usage539- Reduce server costs540541### Implementation with Express Rate Limit542543\`\`\`javascript544const rateLimit = require('express-rate-limit');545const RedisStore = require('rate-limit-redis');546const Redis = require('ioredis');547548// Create Redis client549const redis = new Redis({550host: process.env.REDIS_HOST,551port: process.env.REDIS_PORT552});553554// General API rate limit555const apiLimiter = rateLimit({556store: new RedisStore({557client: redis,558prefix: 'rl:api:'559}),560windowMs: 15 * 60 * 1000, // 15 minutes561max: 100, // 100 requests per window562message: {563error: 'Too many requests, please try again later',564retryAfter: 900 // seconds565},566standardHeaders: true, // Return rate limit info in headers567legacyHeaders: false,568// Custom key generator (by user ID or IP)569keyGenerator: (req) => {570return req.user?.userId || req.ip;571}572});573574// Strict rate limit for authentication endpoints575const authLimiter = rateLimit({576store: new RedisStore({577client: redis,578prefix: 'rl:auth:'579}),580windowMs: 15 * 60 * 1000, // 15 minutes581max: 5, // Only 5 login attempts per 15 minutes582skipSuccessfulRequests: true, // Don't count successful logins583message: {584error: 'Too many login attempts, please try again later',585retryAfter: 900586}587});588589// Apply rate limiters590app.use('/api/', apiLimiter);591app.use('/api/auth/login', authLimiter);592app.use('/api/auth/register', authLimiter);593594// Custom rate limiter for expensive operations595const expensiveLimiter = rateLimit({596windowMs: 60 * 60 * 1000, // 1 hour597max: 10, // 10 requests per hour598message: {599error: 'Rate limit exceeded for this operation'600}601});602603app.post('/api/reports/generate',604authenticateToken,605expensiveLimiter,606async (req, res) => {607// Expensive operation608}609);610\`\`\`611612### Advanced: Per-User Rate Limiting613614\`\`\`javascript615// Different limits based on user tier616function createTieredRateLimiter() {617const limits = {618free: { windowMs: 60 * 60 * 1000, max: 100 },619pro: { windowMs: 60 * 60 * 1000, max: 1000 },620enterprise: { windowMs: 60 * 60 * 1000, max: 10000 }621};622623return async (req, res, next) => {624const user = req.user;625const tier = user?.tier || 'free';626const limit = limits[tier];627628const key = \`rl:user:\${user.userId}\`;629const current = await redis.incr(key);630631if (current === 1) {632await redis.expire(key, limit.windowMs / 1000);633}634635if (current > limit.max) {636return res.status(429).json({637error: 'Rate limit exceeded',638limit: limit.max,639remaining: 0,640reset: await redis.ttl(key)641});642}643644// Set rate limit headers645res.set({646'X-RateLimit-Limit': limit.max,647'X-RateLimit-Remaining': limit.max - current,648'X-RateLimit-Reset': await redis.ttl(key)649});650651next();652};653}654655app.use('/api/', authenticateToken, createTieredRateLimiter());656\`\`\`657658### DDoS Protection with Helmet659660\`\`\`javascript661const helmet = require('helmet');662663app.use(helmet({664// Content Security Policy665contentSecurityPolicy: {666directives: {667defaultSrc: ["'self'"],668styleSrc: ["'self'", "'unsafe-inline'"],669scriptSrc: ["'self'"],670imgSrc: ["'self'", 'data:', 'https:']671}672},673// Prevent clickjacking674frameguard: { action: 'deny' },675// Hide X-Powered-By header676hidePoweredBy: true,677// Prevent MIME type sniffing678noSniff: true,679// Enable HSTS680hsts: {681maxAge: 31536000,682includeSubDomains: true,683preload: true684}685}));686\`\`\`687688### Rate Limit Response Headers689690\`\`\`691X-RateLimit-Limit: 100692X-RateLimit-Remaining: 87693X-RateLimit-Reset: 1640000000694Retry-After: 900695\`\`\`696```697698## Best Practices699700### ✅ Do This701702- **Use HTTPS Everywhere** - Never send sensitive data over HTTP703- **Implement Authentication** - Require authentication for protected endpoints704- **Validate All Inputs** - Never trust user input705- **Use Parameterized Queries** - Prevent SQL injection706- **Implement Rate Limiting** - Protect against brute force and DDoS707- **Hash Passwords** - Use bcrypt with salt rounds >= 10708- **Use Short-Lived Tokens** - JWT access tokens should expire quickly709- **Implement CORS Properly** - Only allow trusted origins710- **Log Security Events** - Monitor for suspicious activity711- **Keep Dependencies Updated** - Regularly update packages712- **Use Security Headers** - Implement Helmet.js713- **Sanitize Error Messages** - Don't leak sensitive information714715### ❌ Don't Do This716717- **Don't Store Passwords in Plain Text** - Always hash passwords718- **Don't Use Weak Secrets** - Use strong, random JWT secrets719- **Don't Trust User Input** - Always validate and sanitize720- **Don't Expose Stack Traces** - Hide error details in production721- **Don't Use String Concatenation for SQL** - Use parameterized queries722- **Don't Store Sensitive Data in JWT** - JWTs are not encrypted723- **Don't Ignore Security Updates** - Update dependencies regularly724- **Don't Use Default Credentials** - Change all default passwords725- **Don't Disable CORS Completely** - Configure it properly instead726- **Don't Log Sensitive Data** - Sanitize logs727728## Common Pitfalls729730### Problem: JWT Secret Exposed in Code731**Symptoms:** JWT secret hardcoded or committed to Git732**Solution:**733\`\`\`javascript734// ❌ Bad735const JWT_SECRET = 'my-secret-key';736737// ✅ Good738const JWT_SECRET = process.env.JWT_SECRET;739if (!JWT_SECRET) {740throw new Error('JWT_SECRET environment variable is required');741}742743// Generate strong secret744// node -e "console.log(require('crypto').randomBytes(64).toString('hex'))"745\`\`\`746747### Problem: Weak Password Requirements748**Symptoms:** Users can set weak passwords like "password123"749**Solution:**750\`\`\`javascript751const passwordSchema = z.string()752.min(12, 'Password must be at least 12 characters')753.regex(/[A-Z]/, 'Must contain uppercase letter')754.regex(/[a-z]/, 'Must contain lowercase letter')755.regex(/[0-9]/, 'Must contain number')756.regex(/[^A-Za-z0-9]/, 'Must contain special character');757758// Or use a password strength library759const zxcvbn = require('zxcvbn');760const result = zxcvbn(password);761if (result.score < 3) {762return res.status(400).json({763error: 'Password too weak',764suggestions: result.feedback.suggestions765});766}767\`\`\`768769### Problem: Missing Authorization Checks770**Symptoms:** Users can access resources they shouldn't771**Solution:**772\`\`\`javascript773// ❌ Bad: Only checks authentication774app.delete('/api/posts/:id', authenticateToken, async (req, res) => {775await prisma.post.delete({ where: { id: req.params.id } });776res.json({ success: true });777});778779// ✅ Good: Checks both authentication and authorization780app.delete('/api/posts/:id', authenticateToken, async (req, res) => {781const post = await prisma.post.findUnique({782where: { id: req.params.id }783});784785if (!post) {786return res.status(404).json({ error: 'Post not found' });787}788789// Check if user owns the post or is admin790if (post.userId !== req.user.userId && req.user.role !== 'admin') {791return res.status(403).json({792error: 'Not authorized to delete this post'793});794}795796await prisma.post.delete({ where: { id: req.params.id } });797res.json({ success: true });798});799\`\`\`800801### Problem: Verbose Error Messages802**Symptoms:** Error messages reveal system details803**Solution:**804\`\`\`javascript805// ❌ Bad: Exposes database details806app.post('/api/users', async (req, res) => {807try {808const user = await prisma.user.create({ data: req.body });809res.json(user);810} catch (error) {811res.status(500).json({ error: error.message });812// Error: "Unique constraint failed on the fields: (`email`)"813}814});815816// ✅ Good: Generic error message817app.post('/api/users', async (req, res) => {818try {819const user = await prisma.user.create({ data: req.body });820res.json(user);821} catch (error) {822console.error('User creation error:', error); // Log full error823824if (error.code === 'P2002') {825return res.status(400).json({826error: 'Email already exists'827});828}829830res.status(500).json({831error: 'An error occurred while creating user'832});833}834});835\`\`\`836837## Security Checklist838839### Authentication & Authorization840- [ ] Implement strong authentication (JWT, OAuth 2.0)841- [ ] Use HTTPS for all endpoints842- [ ] Hash passwords with bcrypt (salt rounds >= 10)843- [ ] Implement token expiration844- [ ] Add refresh token mechanism845- [ ] Verify user authorization for each request846- [ ] Implement role-based access control (RBAC)847848### Input Validation849- [ ] Validate all user inputs850- [ ] Use parameterized queries or ORM851- [ ] Sanitize HTML content852- [ ] Validate file uploads853- [ ] Implement request schema validation854- [ ] Use allowlists, not blocklists855856### Rate Limiting & DDoS Protection857- [ ] Implement rate limiting per user/IP858- [ ] Add stricter limits for auth endpoints859- [ ] Use Redis for distributed rate limiting860- [ ] Return proper rate limit headers861- [ ] Implement request throttling862863### Data Protection864- [ ] Use HTTPS/TLS for all traffic865- [ ] Encrypt sensitive data at rest866- [ ] Don't store sensitive data in JWT867- [ ] Sanitize error messages868- [ ] Implement proper CORS configuration869- [ ] Use security headers (Helmet.js)870871### Monitoring & Logging872- [ ] Log security events873- [ ] Monitor for suspicious activity874- [ ] Set up alerts for failed auth attempts875- [ ] Track API usage patterns876- [ ] Don't log sensitive data877878## OWASP API Security Top 108798801. **Broken Object Level Authorization** - Always verify user can access resource8812. **Broken Authentication** - Implement strong authentication mechanisms8823. **Broken Object Property Level Authorization** - Validate which properties user can access8834. **Unrestricted Resource Consumption** - Implement rate limiting and quotas8845. **Broken Function Level Authorization** - Verify user role for each function8856. **Unrestricted Access to Sensitive Business Flows** - Protect critical workflows8867. **Server Side Request Forgery (SSRF)** - Validate and sanitize URLs8878. **Security Misconfiguration** - Use security best practices and headers8889. **Improper Inventory Management** - Document and secure all API endpoints88910. **Unsafe Consumption of APIs** - Validate data from third-party APIs890891## Related Skills892893- `@ethical-hacking-methodology` - Security testing perspective894- `@sql-injection-testing` - Testing for SQL injection895- `@xss-html-injection` - Testing for XSS vulnerabilities896- `@broken-authentication` - Authentication vulnerabilities897- `@backend-dev-guidelines` - Backend development standards898- `@systematic-debugging` - Debug security issues899900## Additional Resources901902- [OWASP API Security Top 10](https://owasp.org/www-project-api-security/)903- [JWT Best Practices](https://tools.ietf.org/html/rfc8725)904- [Express Security Best Practices](https://expressjs.com/en/advanced/best-practice-security.html)905- [Node.js Security Checklist](https://blog.risingstack.com/node-js-security-checklist/)906- [API Security Checklist](https://github.com/shieldfy/API-Security-Checklist)907908---909910**Pro Tip:** Security is not a one-time task - regularly audit your APIs, keep dependencies updated, and stay informed about new vulnerabilities!911912## Limitations913- Use this skill only when the task clearly matches the scope described above.914- Do not treat the output as a substitute for environment-specific validation, testing, or expert review.915- Stop and ask for clarification if required inputs, permissions, safety boundaries, or success criteria are missing.916