Complete API Security Testing Checklist: Comprehensive Guide
APIs are the backbone of modern applications, but they're also prime targets for cyberattacks. A single vulnerability in your API can lead to data breaches, unauthorized access, and complete system compromise. Here's everything you need to test and secure your APIs against real-world threats.
Understanding API Security Threats
Before diving into testing, it's crucial to understand the threats APIs face:
Common API Attack Vectors
- Injection Attacks: SQL, NoSQL, LDAP, and command injection
- Broken Authentication: Weak tokens, session management flaws
- Broken Authorization: IDOR, privilege escalation, access control bypass
- Data Exposure: Sensitive data in responses, logs, or error messages
- Rate Limiting Bypass: DoS attacks, brute force attempts
- Input Validation: Malicious payloads, oversized requests
- Business Logic Flaws: Workflow bypass, state manipulation
Comprehensive API Security Testing Framework
1. Authentication and Session Management
Strong Authentication Implementation
// Example: Secure JWT implementation
const jwt = require('jsonwebtoken');
// Generate secure token
function generateAccessToken(user) {
return jwt.sign(
{
userId: user.id,
role: user.role,
iat: Math.floor(Date.now() / 1000)
},
process.env.JWT_SECRET,
{
expiresIn: '15m',
algorithm: 'HS256',
issuer: 'your-api',
audience: 'your-app'
}
);
}
// Validate token
function validateToken(token) {
try {
return jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'],
issuer: 'your-api',
audience: 'your-app'
});
} catch (error) {
throw new Error('Invalid token');
}
}
Testing Authentication Security
Test Cases:
-
Token Validation
# Test with invalid token curl -H "Authorization: Bearer invalid-token" https://api.example.com/users # Test with expired token curl -H "Authorization: Bearer expired-token" https://api.example.com/users # Test without token curl https://api.example.com/users -
Token Rotation
// Test token refresh mechanism describe('Token Refresh', () => { test('should rotate refresh tokens', async () => { const response = await request(app) .post('/api/auth/refresh') .set('Authorization', `Bearer ${validRefreshToken}`); expect(response.body.accessToken).toBeDefined(); expect(response.body.refreshToken).toBeDefined(); expect(response.body.refreshToken).not.toBe(validRefreshToken); }); }); -
Multi-Factor Authentication
// Test MFA enforcement test('should require MFA for sensitive operations', async () => { const response = await request(app) .post('/api/admin/users') .set('Authorization', `Bearer ${userToken}`) .send({ email: '[email protected]' }); expect(response.status).toBe(403); expect(response.body.error).toContain('MFA required'); });
Session Management Best Practices
- Use HttpOnly, Secure cookies for session tokens
- Implement proper session timeout
- Use secure session storage
- Implement concurrent session limits
// Secure session configuration
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true,
httpOnly: true,
maxAge: 15 * 60 * 1000, // 15 minutes
sameSite: 'strict'
},
store: new RedisStore({
host: 'localhost',
port: 6379,
ttl: 900 // 15 minutes
})
}));
2. Authorization and Access Control
Insecure Direct Object Reference (IDOR) Testing
Test Cases:
# Test IDOR vulnerability
# User A tries to access User B's data
curl -H "Authorization: Bearer user-a-token" https://api.example.com/users/123/orders
curl -H "Authorization: Bearer user-b-token" https://api.example.com/users/123/orders
# Test with different user IDs
curl -H "Authorization: Bearer user-token" https://api.example.com/users/999/orders
curl -H "Authorization: Bearer user-token" https://api.example.com/users/1/orders
Secure Implementation:
// Secure resource access control
app.get('/api/users/:userId/orders', authenticateToken, async (req, res) => {
const { userId } = req.params;
const requestingUser = req.user;
// Enforce ownership check
if (requestingUser.id !== parseInt(userId) && requestingUser.role !== 'admin') {
return res.status(403).json({ error: 'Access denied' });
}
const orders = await Order.findByUserId(userId);
res.json(orders);
});
Role-Based Access Control (RBAC) Testing
// Test role-based access
describe('RBAC Testing', () => {
test('admin can access all resources', async () => {
const response = await request(app)
.get('/api/admin/users')
.set('Authorization', `Bearer ${adminToken}`);
expect(response.status).toBe(200);
});
test('user cannot access admin resources', async () => {
const response = await request(app)
.get('/api/admin/users')
.set('Authorization', `Bearer ${userToken}`);
expect(response.status).toBe(403);
});
});
3. Input Validation and Data Sanitization
Comprehensive Input Validation
// Input validation middleware
const { body, validationResult } = require('express-validator');
const validateUserInput = [
body('email').isEmail().normalizeEmail(),
body('password').isLength({ min: 8 }).matches(/^(?=.*[a-z])(?=.*[A-Z])(?=.*\d)(?=.*[@$!%*?&])[A-Za-z\d@$!%*?&]/),
body('age').isInt({ min: 0, max: 120 }),
body('role').isIn(['user', 'admin', 'moderator']),
(req, res, next) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
next();
}
];
SQL Injection Testing
# Test for SQL injection
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "password", "age": "1 OR 1=1"}'
# Test with malicious payloads
curl -X POST https://api.example.com/search \
-H "Content-Type: application/json" \
-d '{"query": "test\"; DROP TABLE users; --"}'
NoSQL Injection Testing
# Test for NoSQL injection
curl -X POST https://api.example.com/login \
-H "Content-Type: application/json" \
-d '{"email": {"$ne": null}, "password": {"$ne": null}}'
# Test with MongoDB operators
curl -X GET "https://api.example.com/users?filter={\"$where\":\"this.password.length > 0\"}"
4. Rate Limiting and Abuse Prevention
Comprehensive Rate Limiting Implementation
const rateLimit = require('express-rate-limit');
const slowDown = require('express-slow-down');
// General API 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,
});
// Strict rate limiting for auth endpoints
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 5, // limit each IP to 5 requests per windowMs
message: 'Too many authentication attempts',
skipSuccessfulRequests: true,
});
// Slow down after rate limit
const speedLimiter = slowDown({
windowMs: 15 * 60 * 1000, // 15 minutes
delayAfter: 50, // allow 50 requests per 15 minutes, then...
delayMs: 500 // begin adding 500ms of delay per request above 50
});
app.use('/api/', generalLimiter);
app.use('/api/auth/', authLimiter);
app.use('/api/', speedLimiter);
Rate Limiting Testing
# Test rate limiting
for i in {1..10}; do
curl -X POST https://api.example.com/auth/login \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "wrong"}'
done
# Test with different IPs (use proxy or different machines)
curl -X POST https://api.example.com/auth/login \
-H "X-Forwarded-For: 192.168.1.100" \
-H "Content-Type: application/json" \
-d '{"email": "[email protected]", "password": "wrong"}'
5. Error Handling and Information Disclosure
Secure Error Handling
// Secure error handling middleware
function errorHandler(err, req, res, next) {
// Log error for debugging
console.error(err.stack);
// Don't expose internal errors
if (err.name === 'ValidationError') {
return res.status(400).json({
error: 'Validation failed',
details: err.details
});
}
if (err.name === 'UnauthorizedError') {
return res.status(401).json({
error: 'Unauthorized'
});
}
// Generic error response
res.status(500).json({
error: 'Internal server error'
});
}
Error Handling Testing
# Test error responses
curl -X GET https://api.example.com/users/999999
# Should return generic error, not stack trace
curl -X POST https://api.example.com/users \
-H "Content-Type: application/json" \
-d '{"invalid": "data"}'
# Should return validation error without internal details
6. Transport Security and Headers
HTTPS Enforcement
// HTTPS enforcement middleware
function enforceHTTPS(req, res, next) {
if (req.header('x-forwarded-proto') !== 'https' && process.env.NODE_ENV === 'production') {
return res.redirect(`https://${req.header('host')}${req.url}`);
}
next();
}
// Security headers middleware
function securityHeaders(req, res, next) {
res.setHeader('X-Content-Type-Options', 'nosniff');
res.setHeader('X-Frame-Options', 'DENY');
res.setHeader('X-XSS-Protection', '1; mode=block');
res.setHeader('Strict-Transport-Security', 'max-age=31536000; includeSubDomains');
res.setHeader('Referrer-Policy', 'strict-origin-when-cross-origin');
next();
}
CORS Configuration
// Secure CORS configuration
const corsOptions = {
origin: function (origin, callback) {
const allowedOrigins = ['https://app.example.com', 'https://admin.example.com'];
if (!origin || allowedOrigins.indexOf(origin) !== -1) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
optionsSuccessStatus: 200,
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Content-Type', 'Authorization']
};
app.use(cors(corsOptions));
7. Business Logic Testing
Workflow Bypass Testing
// Test business logic vulnerabilities
describe('Business Logic Testing', () => {
test('should not allow negative payments', async () => {
const response = await request(app)
.post('/api/payments')
.set('Authorization', `Bearer ${userToken}`)
.send({
amount: -100,
recipient: '[email protected]'
});
expect(response.status).toBe(400);
});
test('should enforce payment limits', async () => {
const response = await request(app)
.post('/api/payments')
.set('Authorization', `Bearer ${userToken}`)
.send({
amount: 1000000, // Exceeds daily limit
recipient: '[email protected]'
});
expect(response.status).toBe(400);
});
});
8. API Versioning and Deprecation
Secure API Versioning
// API versioning middleware
function apiVersioning(req, res, next) {
const version = req.header('API-Version') || 'v1';
if (version === 'v1') {
req.apiVersion = 'v1';
} else if (version === 'v2') {
req.apiVersion = 'v2';
} else {
return res.status(400).json({
error: 'Unsupported API version',
supportedVersions: ['v1', 'v2']
});
}
next();
}
9. Monitoring and Logging
Comprehensive API Logging
// API logging middleware
function apiLogger(req, res, next) {
const start = Date.now();
res.on('finish', () => {
const duration = Date.now() - start;
const logData = {
timestamp: new Date().toISOString(),
method: req.method,
url: req.url,
statusCode: res.statusCode,
duration: duration,
userAgent: req.get('User-Agent'),
ip: req.ip,
userId: req.user?.id
};
// Log to secure logging system
logger.info('API Request', logData);
// Alert on suspicious activity
if (res.statusCode >= 400 || duration > 5000) {
alerting.sendAlert('API Issue', logData);
}
});
next();
}
10. Automated Security Testing
API Security Test Suite
// Comprehensive API security tests
describe('API Security Tests', () => {
describe('Authentication', () => {
test('should reject invalid tokens', async () => {
const response = await request(app)
.get('/api/users')
.set('Authorization', 'Bearer invalid-token');
expect(response.status).toBe(401);
});
test('should enforce token expiration', async () => {
const expiredToken = generateExpiredToken();
const response = await request(app)
.get('/api/users')
.set('Authorization', `Bearer ${expiredToken}`);
expect(response.status).toBe(401);
});
});
describe('Authorization', () => {
test('should prevent IDOR attacks', async () => {
const response = await request(app)
.get('/api/users/999/orders')
.set('Authorization', `Bearer ${userToken}`);
expect(response.status).toBe(403);
});
});
describe('Input Validation', () => {
test('should prevent SQL injection', async () => {
const response = await request(app)
.post('/api/search')
.send({ query: "'; DROP TABLE users; --" });
expect(response.status).toBe(400);
});
});
});
OWASP API Security Top 10 Alignment
API1: Broken Object Level Authorization
- Test IDOR on every resource endpoint
- Verify ownership checks for all operations
- Test with different user contexts
API2: Broken Authentication
- Test token validation and expiration
- Verify secure token generation
- Test authentication bypass attempts
API3: Broken Object Property Level Authorization
- Test field-level access controls
- Verify sensitive field filtering
- Test property manipulation
API4: Unrestricted Resource Consumption
- Test rate limiting effectiveness
- Verify resource quotas
- Test DoS attack scenarios
API5: Broken Function Level Authorization
- Test role-based access controls
- Verify function-level permissions
- Test privilege escalation
API6: Unrestricted Access to Sensitive Business Flows
- Test business logic workflows
- Verify state transitions
- Test workflow bypass attempts
API7: Server Side Request Forgery (SSRF)
- Test URL parameter validation
- Verify internal network access
- Test metadata endpoint access
API8: Security Misconfiguration
- Test security headers
- Verify CORS configuration
- Test error handling
API9: Improper Inventory Management
- Test API versioning
- Verify deprecated endpoint handling
- Test documentation accuracy
API10: Unsafe Consumption of APIs
- Test third-party API integration
- Verify input validation
- Test error handling
Continuous Security Monitoring
Real-time Security Monitoring
// Security monitoring middleware
function securityMonitoring(req, res, next) {
// Monitor for suspicious patterns
const suspiciousPatterns = [
/union.*select/i,
/script.*alert/i,
/\.\.\//,
/<script/i
];
const requestBody = JSON.stringify(req.body);
const requestQuery = JSON.stringify(req.query);
for (const pattern of suspiciousPatterns) {
if (pattern.test(requestBody) || pattern.test(requestQuery)) {
logger.warn('Suspicious request detected', {
ip: req.ip,
url: req.url,
body: req.body,
query: req.query
});
// Optionally block the request
return res.status(400).json({ error: 'Invalid request' });
}
}
next();
}
Testing workflow
# Example: probing authz on a REST endpoint
curl -i -H "Authorization: Bearer <token-of-user-A>" https://api.example.com/v1/orders/123
# Then try with a different user token; expect 403 if not owned
GraphQL:
{
order(id: 123) { id userId total }
}
Ensure resolvers enforce ownership checks, not just schema-level types.
Common pitfalls
- CORS wide open with credentials
- Putting secrets in mobile apps or SPA bundles
- Long‑lived tokens without rotation
Advanced tips
- Log and alert on authz denials by resource type to discover IDOR attempts
- Use structured scopes; avoid "god" tokens
- Threat‑model integrations and webhooks
OWASP API Top 10 alignment (brief)
- API1: Broken Object Level Authorization (test IDOR on every resource)
- API2: Broken Authentication (short‑lived tokens, MFA for critical paths)
- API3: Broken Object Property Level Authorization (reject unknown/extra fields)
- API4: Unrestricted Resource Consumption (rate limits, timeouts)
- API5: Broken Function Level Authorization (verify action-level permissions)
- API6: Unrestricted Access to Sensitive Business Flows (protect important workflows)
- API7: Server Side Request Forgery (validate outbound calls, allowlists)
- API8: Security Misconfiguration (disable debug, strict CORS, headers)
- API9: Improper Inventory Management (document versions, deprecate safely)
- API10: Unsafe Consumption of APIs (validate third-party data; timeouts/retries)
Pair this with ongoing monitoring from the Web security monitoring guide and deeper testing from the Penetration testing guide.
Conclusion
Work through this checklist regularly and before major releases. Close easy gaps first, then refine limits and scopes.
Run focused checks and watch for regressions in the Barrion dashboard. For a quick look at your public surface, try the Network Security tool.