Skip to main content

api-security-checklist

Comprehensive API security checklist and best practices for designing, testing, and securing REST, GraphQL, and OAuth APIs

설치로 이동

소스 정보

저장소
reason-machines/security-skills
최근 소스 활동
2026년 7월 30일 22:52
감지된 SKILL.md 언어
영어
스타
12
포크
1

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
api-security-checklist
description
Comprehensive API security checklist and best practices for designing, testing, and securing REST, GraphQL, and OAuth APIs
triggers
["show me API security best practices","how do I secure my REST API","what security checks should I implement for my API","help me review API security","OAuth security recommendations","API authentication and authorization checklist","protect my API from attacks","API security audit checklist"]
# API Security Checklist Skill > Skill by [ara.so](https://ara.so) — Security Skills collection. This skill provides comprehensive guidance on API security best practices based on the widely-adopted API Security Checklist. Use this to design, audit, and secure REST, GraphQL, and OAuth APIs against common vulnerabilities and attack vectors. ## Overview The API Security Checklist covers critical security countermeasures across: - **Authentication** - Secure user identity verification - **Authorization** - Access control and OAuth flows - **Input Validation** - Preventing injection attacks - **Output Security** - Secure response handling - **Processing** - Backend security measures - **Monitoring** - Detection and alerting - **CI/CD** - Secure development lifecycle ## Installation This is a knowledge resource, not a software package. To use: 1. **Bookmark for reference**: Keep the checklist accessible during API development 2. **Integrate into code reviews**: Use as a PR checklist template 3. **Add to CI/CD**: Implement automated checks based on these guidelines 4. **Security audits**: Use as an audit framework ## Authentication Security ### ❌ Avoid Basic Auth ```javascript // BAD - Basic Auth is insecure app.get('/api/users', (req, res) => { const auth = req.headers.authorization; const [user, pass] = Buffer.from(auth.split(' ')[1], 'base64').toString().split(':'); // Don't do this! }); ``` ### ✅ Use Standard Authentication ```javascript // GOOD - JWT with proper validation const jwt = require('jsonwebtoken'); function authenticateToken(req, res, next) { const token = req.headers['authorization']?.split(' ')[1]; if (!token) return res.sendStatus(401); jwt.verify(token, process.env.JWT_SECRET, (err, user) => { if (err) return res.sendStatus(403); req.user = user; next(); }); } app.get('/api/users', authenticateToken, (req, res) => { res.json({ user: req.user }); }); ``` ### Rate Limiting and Max Retry ```javascript const rateLimit = require('express-rate-limit'); // Limit login attempts const loginLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, // 5 attempts message: 'Too many login attempts, please try again later', standardHeaders: true, legacyHeaders: false, }); app.post('/api/login', loginLimiter, async (req, res) => { // Login logic }); ``` ### Password Storage ```javascript const bcrypt = require('bcrypt'); // GOOD - Hash passwords with bcrypt async function hashPassword(password) { const saltRounds = 12; return await bcrypt.hash(password, saltRounds); } async function verifyPassword(password, hash) { return await bcrypt.compare(password, hash); } // Usage app.post('/api/register', async (req, res) => { const { email, password } = req.body; const hashedPassword = await hashPassword(password); // Store hashedPassword in database }); ``` ## Access Control ### HTTPS and Security Headers ```javascript const helmet = require('helmet'); const express = require('express'); const app = express(); // Use Helmet for security headers app.use(helmet({ hsts: { maxAge: 31536000, includeSubDomains: true, preload: true }, contentSecurityPolicy: { directives: { defaultSrc: ["'none'"] } }, frameguard: { action: 'deny' }, noSniff: true })); // Force HTTPS in production if (process.env.NODE_ENV === 'production') { app.use((req, res, next) => { if (!req.secure) { return res.redirect('https://' + req.headers.host + req.url); } next(); }); } ``` ### IP Whitelisting for Private APIs ```javascript const ipWhitelist = process.env.ALLOWED_IPS?.split(',') || []; function checkIPWhitelist(req, res, next) { const clientIP = req.ip || req.connection.remoteAddress; if (!ipWhitelist.includes(clientIP)) { return res.status(403).json({ error: 'IP not authorized' }); } next(); } app.use('/api/admin', checkIPWhitelist); ``` ### DDoS Protection ```javascript const rateLimit = require('express-rate-limit'); // General API rate limiting const apiLimiter = rateLimit({ windowMs: 1 * 60 * 1000, // 1 minute max: 100, // 100 requests per minute message: 'Too many requests from this IP' }); app.use('/api/', apiLimiter); ``` ## OAuth Security ### Validate Redirect URI ```python from urllib.parse import urlparse ALLOWED_REDIRECT_URIS = [ 'https://app.example.com/callback', 'https://app.example.com/oauth/callback' ] def validate_redirect_uri(redirect_uri): """Always validate redirect_uri server-side""" if redirect_uri not in ALLOWED_REDIRECT_URIS: raise ValueError('Invalid redirect_uri') return True # In OAuth authorization endpoint @app.route('/oauth/authorize') def authorize(): redirect_uri = request.args.get('redirect_uri') try: validate_redirect_uri(redirect_uri) except ValueError: return {'error': 'invalid_redirect_uri'}, 400 # Continue with authorization ``` ### Use State Parameter for CSRF Protection ```javascript const crypto = require('crypto'); // Generate state parameter function generateState() { return crypto.randomBytes(32).toString('hex'); } // OAuth authorization request app.get('/oauth/login', (req, res) => { const state = generateState(); // Store state in session req.session.oauthState = state; const authUrl = `https://provider.com/oauth/authorize?` + `client_id=${process.env.OAUTH_CLIENT_ID}` + `&redirect_uri=${encodeURIComponent(process.env.OAUTH_REDIRECT_URI)}` + `&response_type=code` + `&state=${state}` + `&scope=read`; res.redirect(authUrl); }); // OAuth callback - validate state app.get('/oauth/callback', (req, res) => { const { code, state } = req.query; // Validate state parameter if (state !== req.session.oauthState) { return res.status(403).json({ error: 'Invalid state parameter' }); } // Exchange code for token // Never use response_type=token (implicit flow) }); ``` ### Scope Validation ```javascript const VALID_SCOPES = ['read', 'write', 'admin']; const DEFAULT_SCOPE = 'read'; function validateScopes(requestedScopes) { if (!requestedScopes) return [DEFAULT_SCOPE]; const scopes = requestedScopes.split(' '); const validScopes = scopes.filter(scope => VALID_SCOPES.includes(scope)); return validScopes.length > 0 ? validScopes : [DEFAULT_SCOPE]; } app.post('/oauth/token', (req, res) => { const requestedScopes = req.body.scope; const allowedScopes = validateScopes(requestedScopes); // Generate token with validated scopes only const token = jwt.sign( { scopes: allowedScopes }, process.env.JWT_SECRET, { expiresIn: '1h' } ); res.json({ access_token: token, scope: allowedScopes.join(' ') }); }); ``` ## Input Validation ### HTTP Method Validation ```javascript const ALLOWED_METHODS = { '/api/users': ['GET', 'POST'], '/api/users/:id': ['GET', 'PUT', 'PATCH', 'DELETE'] }; function validateMethod(req, res, next) { const allowedForRoute = ALLOWED_METHODS[req.route.path]; if (!allowedForRoute || !allowedForRoute.includes(req.method)) { res.set('Allow', allowedForRoute.join(', ')); return res.status(405).json({ error: 'Method Not Allowed' }); } next(); } app.use(validateMethod); ``` ### Content-Type Validation ```javascript const SUPPORTED_CONTENT_TYPES = [ 'application/json', 'application/xml' ]; function validateContentType(req, res, next) { // Validate Accept header const accept = req.headers.accept; const acceptsSupported = SUPPORTED_CONTENT_TYPES.some(type => accept?.includes(type) ); if (!acceptsSupported && accept !== '*/*') { return res.status(406).json({ error: 'Not Acceptable' }); } // Validate Content-Type for POST/PUT/PATCH if (['POST', 'PUT', 'PATCH'].includes(req.method)) { const contentType = req.headers['content-type']?.split(';')[0]; if (!SUPPORTED_CONTENT_TYPES.includes(contentType)) { return res.status(415).json({ error: 'Unsupported Media Type' }); } } next(); } app.use(validateContentType); ``` ### Input Sanitization ```javascript const validator = require('validator'); // Prevent XSS, SQL Injection, etc. function sanitizeInput(data) { if (typeof data === 'string') { return validator.escape(data); } if (Array.isArray(data)) { return data.map(sanitizeInput); } if (typeof data === 'object' && data !== null) { const sanitized = {}; for (const [key, value] of Object.entries(data)) { sanitized[key] = sanitizeInput(value); } return sanitized; } return data; } app.post('/api/users', (req, res) => { const sanitizedBody = sanitizeInput(req.body); // Use sanitizedBody instead of req.body }); ``` ### Prevent XXE (XML External Entity) ```javascript const libxmljs = require('libxmljs'); function parseXMLSafely(xmlString) { try { // Disable external entity parsing const doc = libxmljs.parseXml(xmlString, { noent: false, // Don't substitute entities nonet: true, // Don't access network dtdload: false // Don't load external DTDs }); return doc; } catch (error) { throw new Error('Invalid XML'); } } app.post('/api/data', (req, res) => { if (req.headers['content-type'] === 'application/xml') { try { const doc = parseXMLSafely(req.body); // Process document } catch (error) { return res.status(400).json({ error: 'Invalid XML' }); } } }); ``` ## Processing Security ### Avoid Auto-Increment IDs (Use UUIDs) ```javascript const { v4: uuidv4 } = require('uuid'); // GOOD - Use UUIDs instead of auto-increment IDs app.post('/api/users', async (req, res) => { const user = { id: uuidv4(), // e.g., '9b1deb4d-3b7d-4bad-9bdd-2b0d7b3dcb6d' ...req.body }; await db.users.create(user); res.json(user); }); ``` ### Use /me for User Resources ```javascript // BAD - Exposes user IDs
GitHub에서 보기
이 SKILL.md는 매우 커서 SkillsMP가 여기에는 첫 섹션만 미리 보여줍니다. GitHub에서 보기