| name | api-security-checker |
| description | Audit API security for OWASP Top 10 vulnerabilities, authentication issues, and authorization flaws. Use when securing APIs, fixing security vulnerabilities, or implementing security best practices. |
API Security Checker
Audit API security and identify vulnerabilities based on OWASP Top 10.
Quick Start
Check authentication, validate inputs, prevent SQL injection, implement rate limiting, use HTTPS.
Instructions
OWASP Top 10 for APIs
1. Broken Object Level Authorization:
app.get('/api/users/:id', (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});
app.get('/api/users/:id', auth, async (req, res) => {
if (req.user.id !== req.params.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
const user = await User.findById(req.params.id);
res.json(user);
});
2. Broken Authentication:
const isValidPassword = (password) => password.length >= 6;
const isValidPassword = (password) => {
return password.length >= 12 &&
/[A-Z]/.test(password) &&
/[a-z]/.test(password) &&
/[0-9]/.test(password) &&
/[^A-Za-z0-9]/.test(password);
};
const loginLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5,
message: 'Too many login attempts'
});
app.post('/api/login', loginLimiter, loginHandler);
3. Excessive Data Exposure:
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id);
res.json(user);
});
app.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id)
.select('id username avatar');
res.json(user);
});
4. Lack of Resources & Rate Limiting:
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 100,
standardHeaders: true,
legacyHeaders: false,
});
app.use('/api/', apiLimiter);
5. Broken Function Level Authorization:
app.delete('/api/users/:id', auth, deleteUser);
app.delete('/api/users/:id', auth, requireAdmin, deleteUser);
function requireAdmin(req, res, next) {
if (!req.user.isAdmin) {
return res.status(403).json({ error: 'Admin access required' });
}
next();
}
6. Mass Assignment:
app.put('/api/users/:id', async (req, res) => {
await User.update(req.params.id, req.body);
});
app.put('/api/users/:id', async (req, res) => {
const { name, email, avatar } = req.body;
await User.update(req.params.id, { name, email, avatar });
});
7. Security Misconfiguration:
const helmet = require('helmet');
app.use(helmet());
app.disable('x-powered-by');
const cors = require('cors');
app.use(cors({
origin: process.env.ALLOWED_ORIGINS.split(','),
credentials: true
}));
8. Injection:
const query = `SELECT * FROM users WHERE email = '${email}'`;
const query = 'SELECT * FROM users WHERE email = $1';
const result = await db.query(query, [email]);
const { body, validationResult } = require('express-validator');
app.post('/api/users',
body('email').isEmail().normalizeEmail(),
body('name').trim().escape(),
async (req, res) => {
const errors = validationResult(req);
if (!errors.isEmpty()) {
return res.status(400).json({ errors: errors.array() });
}
}
);
9. Improper Assets Management:
app.use('/api/v1', v1Routes);
app.use('/api/v2', v2Routes);
app.use('/api/v1', (req, res, next) => {
res.set('Deprecation', 'true');
res.set('Sunset', 'Sat, 31 Dec 2024 23:59:59 GMT');
next();
});
10. Insufficient Logging & Monitoring:
const winston = require('winston');
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'error.log', level: 'error' }),
new winston.transports.File({ filename: 'combined.log' })
]
});
app.post('/api/login', async (req, res) => {
try {
const user = await authenticate(req.body);
logger.info('Login successful', { userId: user.id, ip: req.ip });
res.json({ token: generateToken(user) });
} catch (error) {
logger.warn('Login failed', { email: req.body.email, ip: req.ip });
res.status(401).json({ error: 'Invalid credentials' });
}
});
Authentication Security
JWT best practices:
const jwt = require('jsonwebtoken');
const generateToken = (user) => {
return jwt.sign(
{ id: user.id, email: user.email },
process.env.JWT_SECRET,
{ expiresIn: '15m' }
);
};
const generateRefreshToken = (user) => {
return jwt.sign(
{ id: user.id },
process.env.REFRESH_SECRET,
{ expiresIn: '7d' }
);
};
const verifyToken = (req, res, next) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) {
return res.status(401).json({ error: 'No token provided' });
}
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded;
next();
} catch (error) {
return res.status(401).json({ error: 'Invalid token' });
}
};
Password hashing:
const bcrypt = require('bcrypt');
const hashPassword = async (password) => {
const salt = await bcrypt.genSalt(12);
return bcrypt.hash(password, salt);
};
const verifyPassword = async (password, hash) => {
return bcrypt.compare(password, hash);
};
Input Validation
Sanitize inputs:
const validator = require('validator');
if (!validator.isEmail(email)) {
return res.status(400).json({ error: 'Invalid email' });
}
if (!validator.isURL(url)) {
return res.status(400).json({ error: 'Invalid URL' });
}
const sanitized = validator.escape(userInput);
Schema validation:
const Joi = require('joi');
const userSchema = Joi.object({
email: Joi.string().email().required(),
password: Joi.string().min(12).required(),
name: Joi.string().min(2).max(50).required()
});
app.post('/api/users', async (req, res) => {
const { error, value } = userSchema.validate(req.body);
if (error) {
return res.status(400).json({ error: error.details[0].message });
}
});
Authorization
Role-Based Access Control (RBAC):
const roles = {
user: ['read:own'],
admin: ['read:any', 'write:any', 'delete:any']
};
const checkPermission = (permission) => {
return (req, res, next) => {
const userPermissions = roles[req.user.role] || [];
if (!userPermissions.includes(permission)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
};
app.delete('/api/users/:id',
auth,
checkPermission('delete:any'),
deleteUser
);
HTTPS and Transport Security
Force HTTPS:
app.use((req, res, next) => {
if (req.header('x-forwarded-proto') !== 'https' && process.env.NODE_ENV === 'production') {
res.redirect(`https://${req.header('host')}${req.url}`);
} else {
next();
}
});
Security headers:
app.use(helmet({
contentSecurityPolicy: {
directives: {
defaultSrc: ["'self'"],
styleSrc: ["'self'", "'unsafe-inline'"],
scriptSrc: ["'self'"],
imgSrc: ["'self'", 'data:', 'https:']
}
},
hsts: {
maxAge: 31536000,
includeSubDomains: true,
preload: true
}
}));
CORS Configuration
const corsOptions = {
origin: (origin, callback) => {
const allowedOrigins = process.env.ALLOWED_ORIGINS.split(',');
if (!origin || allowedOrigins.includes(origin)) {
callback(null, true);
} else {
callback(new Error('Not allowed by CORS'));
}
},
credentials: true,
optionsSuccessStatus: 200
};
app.use(cors(corsOptions));
API Key Security
const apiKeyAuth = (req, res, next) => {
const apiKey = req.header('X-API-Key');
if (!apiKey) {
return res.status(401).json({ error: 'API key required' });
}
const isValid = await verifyApiKey(apiKey);
if (!isValid) {
return res.status(401).json({ error: 'Invalid API key' });
}
next();
};
Security Checklist
Authentication:
Authorization:
Input Validation:
Transport Security:
API Security:
Data Protection:
Best Practices
Defense in depth:
- Multiple security layers
- Fail securely
- Least privilege
- Regular updates
Secure defaults:
- HTTPS required
- Strong authentication
- Input validation
- Rate limiting enabled
Monitoring:
- Log security events
- Monitor for anomalies
- Alert on suspicious activity
- Regular security audits