home / skills / aj-geddes / useful-ai-prompts / api-security-hardening

api-security-hardening skill

/skills/api-security-hardening

This skill helps you harden REST APIs by enforcing authentication, rate limiting, input validation, CORS, and security middleware across endpoints.

npx playbooks add skill aj-geddes/useful-ai-prompts --skill api-security-hardening

Review the files below or copy the command above to add this skill to your agents.

Files (1)
SKILL.md
18.0 KB
---
name: api-security-hardening
description: Secure REST APIs with authentication, rate limiting, CORS, input validation, and security middleware. Use when building or hardening API endpoints against common attacks.
---

# API Security Hardening

## Overview

Implement comprehensive API security measures including authentication, authorization, rate limiting, input validation, and attack prevention to protect against common vulnerabilities.

## When to Use

- New API development
- Security audit remediation
- Production API hardening
- Compliance requirements
- High-traffic API protection
- Public API exposure

## Implementation Examples

### 1. **Node.js/Express API Security**

```javascript
// secure-api.js - Comprehensive API security
const express = require('express');
const helmet = require('helmet');
const rateLimit = require('express-rate-limit');
const mongoSanitize = require('express-mongo-sanitize');
const xss = require('xss-clean');
const hpp = require('hpp');
const cors = require('cors');
const jwt = require('jsonwebtoken');
const validator = require('validator');

class SecureAPIServer {
  constructor() {
    this.app = express();
    this.setupSecurityMiddleware();
    this.setupRoutes();
  }

  setupSecurityMiddleware() {
    // 1. Helmet - Set security headers
    this.app.use(helmet({
      contentSecurityPolicy: {
        directives: {
          defaultSrc: ["'self'"],
          styleSrc: ["'self'", "'unsafe-inline'"],
          scriptSrc: ["'self'"],
          imgSrc: ["'self'", "data:", "https:"]
        }
      },
      hsts: {
        maxAge: 31536000,
        includeSubDomains: true,
        preload: true
      }
    }));

    // 2. CORS configuration
    const corsOptions = {
      origin: (origin, callback) => {
        const whitelist = [
          'https://example.com',
          'https://app.example.com'
        ];

        if (!origin || whitelist.includes(origin)) {
          callback(null, true);
        } else {
          callback(new Error('Not allowed by CORS'));
        }
      },
      credentials: true,
      optionsSuccessStatus: 200,
      methods: ['GET', 'POST', 'PUT', 'DELETE'],
      allowedHeaders: ['Content-Type', 'Authorization']
    };

    this.app.use(cors(corsOptions));

    // 3. Rate limiting
    const generalLimiter = rateLimit({
      windowMs: 15 * 60 * 1000, // 15 minutes
      max: 100, // limit each IP to 100 requests per windowMs
      message: 'Too many requests from this IP',
      standardHeaders: true,
      legacyHeaders: false,
      handler: (req, res) => {
        res.status(429).json({
          error: 'rate_limit_exceeded',
          message: 'Too many requests, please try again later',
          retryAfter: req.rateLimit.resetTime
        });
      }
    });

    const authLimiter = rateLimit({
      windowMs: 15 * 60 * 1000,
      max: 5, // Stricter limit for auth endpoints
      skipSuccessfulRequests: true
    });

    this.app.use('/api/', generalLimiter);
    this.app.use('/api/auth/', authLimiter);

    // 4. Body parsing with size limits
    this.app.use(express.json({ limit: '10kb' }));
    this.app.use(express.urlencoded({ extended: true, limit: '10kb' }));

    // 5. NoSQL injection prevention
    this.app.use(mongoSanitize());

    // 6. XSS protection
    this.app.use(xss());

    // 7. HTTP Parameter Pollution prevention
    this.app.use(hpp());

    // 8. Request ID for tracking
    this.app.use((req, res, next) => {
      req.id = require('crypto').randomUUID();
      res.setHeader('X-Request-ID', req.id);
      next();
    });

    // 9. Security logging
    this.app.use(this.securityLogger());
  }

  securityLogger() {
    return (req, res, next) => {
      const startTime = Date.now();

      res.on('finish', () => {
        const duration = Date.now() - startTime;

        const logEntry = {
          timestamp: new Date().toISOString(),
          requestId: req.id,
          method: req.method,
          path: req.path,
          statusCode: res.statusCode,
          duration,
          ip: req.ip,
          userAgent: req.get('user-agent')
        };

        // Log suspicious activity
        if (res.statusCode === 401 || res.statusCode === 403) {
          console.warn('Security event:', logEntry);
        }

        if (res.statusCode >= 500) {
          console.error('Server error:', logEntry);
        }
      });

      next();
    };
  }

  // JWT authentication middleware
  authenticateJWT() {
    return (req, res, next) => {
      const authHeader = req.headers.authorization;

      if (!authHeader || !authHeader.startsWith('Bearer ')) {
        return res.status(401).json({
          error: 'unauthorized',
          message: 'Missing or invalid authorization header'
        });
      }

      const token = authHeader.substring(7);

      try {
        const decoded = jwt.verify(token, process.env.JWT_SECRET, {
          algorithms: ['HS256'],
          issuer: 'api.example.com',
          audience: 'api.example.com'
        });

        req.user = decoded;
        next();
      } catch (error) {
        if (error.name === 'TokenExpiredError') {
          return res.status(401).json({
            error: 'token_expired',
            message: 'Token has expired'
          });
        }

        return res.status(401).json({
          error: 'invalid_token',
          message: 'Invalid token'
        });
      }
    };
  }

  // Input validation middleware
  validateInput(schema) {
    return (req, res, next) => {
      const errors = [];

      // Validate request body
      if (schema.body) {
        for (const [field, rules] of Object.entries(schema.body)) {
          const value = req.body[field];

          if (rules.required && !value) {
            errors.push(`${field} is required`);
            continue;
          }

          if (value) {
            // Type validation
            if (rules.type === 'email' && !validator.isEmail(value)) {
              errors.push(`${field} must be a valid email`);
            }

            if (rules.type === 'uuid' && !validator.isUUID(value)) {
              errors.push(`${field} must be a valid UUID`);
            }

            if (rules.type === 'url' && !validator.isURL(value)) {
              errors.push(`${field} must be a valid URL`);
            }

            // Length validation
            if (rules.minLength && value.length < rules.minLength) {
              errors.push(`${field} must be at least ${rules.minLength} characters`);
            }

            if (rules.maxLength && value.length > rules.maxLength) {
              errors.push(`${field} must be at most ${rules.maxLength} characters`);
            }

            // Pattern validation
            if (rules.pattern && !rules.pattern.test(value)) {
              errors.push(`${field} format is invalid`);
            }
          }
        }
      }

      if (errors.length > 0) {
        return res.status(400).json({
          error: 'validation_error',
          message: 'Input validation failed',
          details: errors
        });
      }

      next();
    };
  }

  // Authorization middleware
  authorize(...roles) {
    return (req, res, next) => {
      if (!req.user) {
        return res.status(401).json({
          error: 'unauthorized',
          message: 'Authentication required'
        });
      }

      if (roles.length > 0 && !roles.includes(req.user.role)) {
        return res.status(403).json({
          error: 'forbidden',
          message: 'Insufficient permissions'
        });
      }

      next();
    };
  }

  setupRoutes() {
    // Public endpoint
    this.app.get('/api/health', (req, res) => {
      res.json({ status: 'healthy' });
    });

    // Protected endpoint with validation
    this.app.post('/api/users',
      this.authenticateJWT(),
      this.authorize('admin'),
      this.validateInput({
        body: {
          email: { required: true, type: 'email' },
          name: { required: true, minLength: 2, maxLength: 100 },
          password: { required: true, minLength: 8 }
        }
      }),
      async (req, res) => {
        try {
          // Sanitized and validated input
          const { email, name, password } = req.body;

          // Process request
          res.status(201).json({
            message: 'User created successfully',
            userId: '123'
          });
        } catch (error) {
          res.status(500).json({
            error: 'internal_error',
            message: 'An error occurred'
          });
        }
      }
    );

    // Error handling middleware
    this.app.use((err, req, res, next) => {
      console.error('Unhandled error:', err);

      res.status(500).json({
        error: 'internal_error',
        message: 'An unexpected error occurred',
        requestId: req.id
      });
    });
  }

  start(port = 3000) {
    this.app.listen(port, () => {
      console.log(`Secure API server running on port ${port}`);
    });
  }
}

// Usage
const server = new SecureAPIServer();
server.start(3000);
```

### 2. **Python FastAPI Security**

```python
# secure_api.py
from fastapi import FastAPI, HTTPException, Depends, Security, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from fastapi.middleware.cors import CORSMiddleware
from fastapi.middleware.trustedhost import TrustedHostMiddleware
from slowapi import Limiter, _rate_limit_exceeded_handler
from slowapi.util import get_remote_address
from slowapi.errors import RateLimitExceeded
from pydantic import BaseModel, EmailStr, validator, Field
import jwt
from datetime import datetime, timedelta
import re
from typing import Optional, List
import secrets

app = FastAPI()
security = HTTPBearer()
limiter = Limiter(key_func=get_remote_address)

# Rate limiting
app.state.limiter = limiter
app.add_exception_handler(RateLimitExceeded, _rate_limit_exceeded_handler)

# CORS configuration
app.add_middleware(
    CORSMiddleware,
    allow_origins=[
        "https://example.com",
        "https://app.example.com"
    ],
    allow_credentials=True,
    allow_methods=["GET", "POST", "PUT", "DELETE"],
    allow_headers=["Content-Type", "Authorization"],
    max_age=3600
)

# Trusted hosts
app.add_middleware(
    TrustedHostMiddleware,
    allowed_hosts=["example.com", "*.example.com"]
)

# Security headers middleware
@app.middleware("http")
async def add_security_headers(request, call_next):
    response = await call_next(request)

    response.headers["X-Content-Type-Options"] = "nosniff"
    response.headers["X-Frame-Options"] = "DENY"
    response.headers["X-XSS-Protection"] = "1; mode=block"
    response.headers["Strict-Transport-Security"] = "max-age=31536000; includeSubDomains"
    response.headers["Content-Security-Policy"] = "default-src 'self'"
    response.headers["Referrer-Policy"] = "strict-origin-when-cross-origin"
    response.headers["Permissions-Policy"] = "geolocation=(), microphone=(), camera=()"

    return response

# Input validation models
class CreateUserRequest(BaseModel):
    email: EmailStr
    name: str = Field(..., min_length=2, max_length=100)
    password: str = Field(..., min_length=8)

    @validator('password')
    def validate_password(cls, v):
        if not re.search(r'[A-Z]', v):
            raise ValueError('Password must contain uppercase letter')
        if not re.search(r'[a-z]', v):
            raise ValueError('Password must contain lowercase letter')
        if not re.search(r'\d', v):
            raise ValueError('Password must contain digit')
        if not re.search(r'[!@#$%^&*]', v):
            raise ValueError('Password must contain special character')
        return v

    @validator('name')
    def validate_name(cls, v):
        # Prevent XSS in name field
        if re.search(r'[<>]', v):
            raise ValueError('Name contains invalid characters')
        return v

class APIKeyRequest(BaseModel):
    name: str = Field(..., max_length=100)
    expires_in_days: int = Field(30, ge=1, le=365)

# JWT token verification
def verify_token(credentials: HTTPAuthorizationCredentials = Security(security)):
    try:
        token = credentials.credentials

        payload = jwt.decode(
            token,
            "your-secret-key",
            algorithms=["HS256"],
            audience="api.example.com",
            issuer="api.example.com"
        )

        return payload

    except jwt.ExpiredSignatureError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Token has expired"
        )
    except jwt.InvalidTokenError:
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid token"
        )

# Role-based authorization
def require_role(required_roles: List[str]):
    def role_checker(token_payload: dict = Depends(verify_token)):
        user_role = token_payload.get('role')

        if user_role not in required_roles:
            raise HTTPException(
                status_code=status.HTTP_403_FORBIDDEN,
                detail="Insufficient permissions"
            )

        return token_payload

    return role_checker

# API key authentication
def verify_api_key(api_key: str):
    # Constant-time comparison to prevent timing attacks
    if not secrets.compare_digest(api_key, "expected-api-key"):
        raise HTTPException(
            status_code=status.HTTP_401_UNAUTHORIZED,
            detail="Invalid API key"
        )
    return True

# Endpoints
@app.get("/api/health")
@limiter.limit("100/minute")
async def health_check():
    return {"status": "healthy"}

@app.post("/api/users")
@limiter.limit("10/minute")
async def create_user(
    user: CreateUserRequest,
    token_payload: dict = Depends(require_role(["admin"]))
):
    """Create new user (admin only)"""

    # Hash password before storing
    # hashed_password = bcrypt.hashpw(user.password.encode(), bcrypt.gensalt())

    return {
        "message": "User created successfully",
        "user_id": "123"
    }

@app.post("/api/keys")
@limiter.limit("5/hour")
async def create_api_key(
    request: APIKeyRequest,
    token_payload: dict = Depends(verify_token)
):
    """Generate API key"""

    # Generate secure random API key
    api_key = secrets.token_urlsafe(32)

    expires_at = datetime.now() + timedelta(days=request.expires_in_days)

    return {
        "api_key": api_key,
        "expires_at": expires_at.isoformat(),
        "name": request.name
    }

@app.get("/api/protected")
async def protected_endpoint(token_payload: dict = Depends(verify_token)):
    return {
        "message": "Access granted",
        "user_id": token_payload.get("sub")
    }

if __name__ == "__main__":
    import uvicorn
    uvicorn.run(app, host="0.0.0.0", port=8000, ssl_certfile="cert.pem", ssl_keyfile="key.pem")
```

### 3. **API Gateway Security Configuration**

```yaml
# nginx-api-gateway.conf
# Nginx API Gateway with security hardening

http {
    # Security headers
    add_header X-Frame-Options "DENY" always;
    add_header X-Content-Type-Options "nosniff" always;
    add_header X-XSS-Protection "1; mode=block" always;
    add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
    add_header Content-Security-Policy "default-src 'self'" always;

    # Rate limiting zones
    limit_req_zone $binary_remote_addr zone=api_limit:10m rate=10r/s;
    limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=1r/s;
    limit_conn_zone $binary_remote_addr zone=conn_limit:10m;

    # Request body size limit
    client_max_body_size 10M;
    client_body_buffer_size 128k;

    # Timeout settings
    client_body_timeout 12;
    client_header_timeout 12;
    send_timeout 10;

    server {
        listen 443 ssl http2;
        server_name api.example.com;

        # SSL configuration
        ssl_certificate /etc/ssl/certs/api.example.com.crt;
        ssl_certificate_key /etc/ssl/private/api.example.com.key;
        ssl_protocols TLSv1.2 TLSv1.3;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;
        ssl_session_cache shared:SSL:10m;
        ssl_session_timeout 10m;

        # API endpoints
        location /api/ {
            # Rate limiting
            limit_req zone=api_limit burst=20 nodelay;
            limit_conn conn_limit 10;

            # CORS headers
            add_header Access-Control-Allow-Origin "https://app.example.com" always;
            add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE" always;
            add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;

            # Block common exploits
            if ($request_method !~ ^(GET|POST|PUT|DELETE|HEAD)$ ) {
                return 444;
            }

            # Proxy to backend
            proxy_pass http://backend:3000;
            proxy_set_header Host $host;
            proxy_set_header X-Real-IP $remote_addr;
            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
            proxy_set_header X-Forwarded-Proto $scheme;

            # Timeouts
            proxy_connect_timeout 60s;
            proxy_send_timeout 60s;
            proxy_read_timeout 60s;
        }

        # Auth endpoints with stricter limits
        location /api/auth/ {
            limit_req zone=auth_limit burst=5 nodelay;

            proxy_pass http://backend:3000;
        }

        # Block access to sensitive files
        location ~ /\. {
            deny all;
            return 404;
        }
    }
}
```

## Best Practices

### ✅ DO
- Use HTTPS everywhere
- Implement rate limiting
- Validate all inputs
- Use security headers
- Log security events
- Implement CORS properly
- Use strong authentication
- Version your APIs

### ❌ DON'T
- Expose stack traces
- Return detailed errors
- Trust user input
- Use HTTP for APIs
- Skip input validation
- Ignore rate limiting

## Security Checklist

- [ ] HTTPS enforced
- [ ] Authentication required
- [ ] Authorization implemented
- [ ] Rate limiting active
- [ ] Input validation
- [ ] CORS configured
- [ ] Security headers set
- [ ] Error handling secure
- [ ] Logging enabled
- [ ] API versioning

## Resources

- [OWASP API Security Top 10](https://owasp.org/www-project-api-security/)
- [API Security Best Practices](https://github.com/shieldfy/API-Security-Checklist)
- [JWT Best Practices](https://tools.ietf.org/html/rfc8725)

Overview

This skill provides practical patterns and middleware for hardening REST APIs against common attacks. It combines authentication, authorization, rate limiting, CORS, input validation, security headers, and logging to make endpoints resilient and auditable. Use it to implement production-ready defenses quickly.

How this skill works

The skill describes middleware and guard implementations that inspect requests for authentication tokens, API keys, and user roles, enforce rate limits, and apply security headers. It shows input validation and sanitization to prevent injection and XSS, enforces CORS and trusted hosts, and logs security-relevant events with request IDs. Error handlers and stricter rules for sensitive routes (auth, admin) are included to limit exposure.

When to use it

  • When building a new public or internal REST API
  • To remediate findings from a security audit or penetration test
  • Before promoting an API to production or high-traffic environments
  • To meet compliance or security baseline requirements
  • When exposing endpoints to third parties or the public

Best practices

  • Apply defense-in-depth: combine headers, validation, rate limiting, and auth rather than relying on one control
  • Use least privilege: restrict routes with role-based authorization and narrow CORS origins
  • Limit request sizes and parse bodies with strict schemas to avoid resource exhaustion and injection
  • Enforce rate limits globally and stricter limits on auth and sensitive endpoints
  • Log security events with request IDs and monitor for repeated 401/403 or high error rates

Example use cases

  • Secure an Express.js API with helmet, CORS whitelist, rateLimit, input validation, and JWT auth
  • Harden a FastAPI service with trusted hosts, security headers, Pydantic validation, and slowapi rate limits
  • Protect authentication endpoints by stricter rate limits and skipSuccessfulRequests for login flows
  • Add per-request IDs and structured security logging to surface suspicious activity and speed incident response
  • Implement role-based endpoints (admin-only user creation) and return clear error codes for clients

FAQ

Does this approach support both token and API key auth?

Yes. Examples cover JWT bearer tokens, role checks, and constant-time API key verification so you can support both models securely.

How do I choose rate limit values?

Pick conservative defaults (e.g., 100 requests/15 minutes) and stricter limits for auth (e.g., 5/15 minutes). Tune based on traffic patterns and use burst/sliding windows if needed.

Will these controls block legitimate clients behind proxies?

Configure trusted proxies and use client IP extraction correctly. For CORS, use an explicit whitelist and allow credentials only when necessary.