API-Sicherheit
Protecting APIs (Application Programming Interfaces) against misuse, unauthorized access, injection attacks, and data leaks. APIs are a modern attack surface: The OWASP API Security Top 10 lists the most common vulnerabilities, ranging from Broken Object-Level Authorization to Rate Limiting.
API security has become a distinct discipline in modern software development. While traditional web security focused on HTML pages, 83% of web applications today communicate primarily via APIs (Akamai State of the Internet 2024).
OWASP API Security Top 10
OWASP has a separate Top 10 list for API security:
API1: Broken Object Level Authorization (BOLA/IDOR)
// Insecure: No owner check
GET /api/orders/4891
// → Attacker changes ID: GET /api/orders/4892 → third-party order visible
// Secure:
app.get('/api/orders/:id', authenticate, async (req, res) => {
const order = await Order.findOne({
_id: req.params.id,
userId: req.user.id // Owner check!
});
if (!order) return res.status(404).send();
res.json(order);
});
API2: Broken Authentication
// Dangerous: Weak JWT algorithm
// Header: {"alg": "none", "typ": "JWT"}
// → The "none" algorithm accepts any token without signature verification!
// Secure: Explicitly specify the algorithm
const token = jwt.verify(req.headers.authorization, process.env.JWT_SECRET, {
algorithms: ['HS256'], // Only accept HS256!
expiresIn: '1h'
});
API3: Broken Object Property Level Authorization
// Insecure: Mass Assignment
app.put('/api/user', authenticate, async (req, res) => {
await User.findByIdAndUpdate(req.user.id, req.body); // Attacker sets isAdmin: true!
});
// Secure: Only allowed fields
const { name, email } = req.body;
await User.findByIdAndUpdate(req.user.id, { name, email }); // isAdmin not possible
API4: Unrestricted Resource Consumption
Without rate limiting:
- Attacker sends 10,000 requests/second
- API costs skyrocket (AWS Lambda, OpenAI API)
- DoS for other users
- Brute-force attacks possible
API5: Broken Function-Level Authorization
// Insecure: Admin endpoint without admin check
app.delete('/api/admin/users/:id', authenticate, async (req, res) => {
await User.findByIdAndDelete(req.params.id); // No admin check!
});
// Secure:
app.delete('/api/admin/users/:id', authenticate, requireAdmin, async (req, res) => {
// requireAdmin middleware checks req.user.role === 'admin'
await User.findByIdAndDelete(req.params.id);
});
API Authentication Best Practices
API Keys
Not suitable for user authentication:
- API keys are shared secrets
- Cannot be revoked without updating all clients
- No scoping (granular permissions are difficult)
Good for: Server-to-server communication, service accounts
Best Practices:
- API key in the header:
Authorization: Bearer sk_live_abc123... - NEVER in the URL:
?api_key=abc123(ends up in logs!) - Separate keys per environment (Dev/Staging/Prod)
- Enable rotation
OAuth 2.0 + OIDC
Modern framework for API authorization:
- Tokens (Access Token, Refresh Token)
- Scopes: Granular permissions (
read:orders,write:orders) - Token Expiry: Short lifespan (15-minute access tokens)
- Revocation: Revoke tokens if compromised
PKCE for Single Page Apps (prevents Authorization Code Interception):
- Code Verifier + Code Challenge
- Secure even without a client secret
JWT Security
// JWT Best Practices:
const token = jwt.sign(
{
userId: user.id,
role: user.role,
// NOT: Password, sensitive data (JWT is base64-decodable!)
},
process.env.JWT_SECRET, // From environment variable, never hardcoded
{
expiresIn: '15m', // Short lifetime
issuer: 'api.company.com',
audience: 'api.company.com',
algorithm: 'HS256' // Specify explicitly
}
);
// Validation:
const decoded = jwt.verify(token, process.env.JWT_SECRET, {
algorithms: ['HS256'], // 'none' is not allowed!
issuer: 'api.company.com',
audience: 'api.company.com'
});
API Rate Limiting
// Express Rate Limiter
const rateLimit = require('express-rate-limit');
const apiLimiter = rateLimit({
windowMs: 15 * 60 * 1000, // 15 minutes
max: 100, // 100 requests per IP
message: 'Too many requests, please try again later.',
standardHeaders: true, // X-RateLimit-* headers
legacyHeaders: false,
});
app.use('/api/', apiLimiter);
// Stricter for authentication:
const authLimiter = rateLimit({
windowMs: 15 * 60 * 1000,
max: 5, // Only 5 login attempts
skipSuccessfulRequests: true, // Only failed attempts count
});
app.use('/api/auth/login', authLimiter);
API Security Testing
For penetration testing or DAST
Tools for API penetration testing:
| Tool | Purpose |
|---|---|
| Burp Suite Pro | Intercepting proxy + scanner + repeater |
| Postman | Manual testing, collection runs |
| OWASP ZAP | Automatic API scan |
| Arjun | Parameter discovery |
| ffuf | Endpoint fuzzing |
Important Tests:
- IDOR: Try all IDs (1, 2, 3, ...)
- Auth Bypass: Omit token, use old token, use third-party token
- Method Tampering: GET instead of POST, HEAD instead of GET
- Verbose Error Messages: Does the API return stack traces?
- Mass Assignment: Send extra fields in the request body
API Security Headers
# Recommended headers for APIs:
add_header X-Content-Type-Options "nosniff";
add_header X-Frame-Options "DENY";
add_header Referrer-Policy "no-referrer";
add_header Permissions-Policy "geolocation=(), microphone=(), camera=()";
# Configure CORS correctly:
# DO NOT: Access-Control-Allow-Origin: *
# Instead:
Access-Control-Allow-Origin: https://app.firma.de
Access-Control-Allow-Methods: GET, POST, PUT, DELETE
Access-Control-Allow-Headers: Authorization, Content-Type