Key Takeaways

  • APIs are the #1 attack vector in modern applications
  • BOLA/IDOR is the most common API vulnerability
  • JWT security requires proper validation and rotation
  • Rate limiting and input validation are non-negotiable

1. The API Security Landscape

APIs have become the backbone of modern applications. Mobile apps, SPAs, microservices, and IoT devices all communicate through APIs. This massive attack surface means API security is now critical. According to Gartner, by 2025, less than 50% of enterprise APIs will be managed, creating massive security blind spots.

Common API types you'll encounter:

2. OWASP API Security Top 10

API1: Broken Object Level Authorization (BOLA)

APIs expose endpoints that handle object identifiers, creating massive IDOR vulnerabilities.

GET /api/users/123/orders
// Change to 456 to access another user's orders
GET /api/users/456/orders

// Prevention: Always verify object ownership
const order = await Order.findById(req.params.id);
if (order.userId !== req.user.id) {
    return res.status(403).json({error: 'Forbidden'});
}
API2: Broken Authentication

Weak authentication mechanisms allow attackers to compromise tokens or exploit implementation flaws.

// Vulnerable: No rate limiting on login
POST /api/login
{"email": "[email protected]", "password": "guess1"}

// Credential stuffing at scale
// Weak JWT without expiration
// Tokens in URL parameters
API3: Broken Object Property Level Authorization

Mass assignment and excessive data exposure in API responses.

// Excessive exposure - returns admin fields
GET /api/users/me
{
    "id": 123,
    "name": "John",
    "email": "[email protected]",
    "role": "user",
    "password_hash": "...",  // Exposed!
    "admin_notes": "..."      // Exposed!
}

// Mass assignment vulnerability
PUT /api/users/me
{"name": "John", "role": "admin"}  // Escalation!

3. Authentication Security

JWT Security Best Practices

// NEVER trust user-controlled algorithm
// Vulnerable to algorithm confusion attack
const decoded = jwt.verify(token, secret);  // Bad if alg not checked

// Secure verification
const decoded = jwt.verify(token, secret, {
    algorithms: ['RS256'],  // Whitelist algorithms
    issuer: 'https://auth.myapp.com',
    audience: 'my-api'
});

// JWT should include:
{
    "iss": "https://auth.myapp.com",
    "sub": "user_123",
    "aud": "my-api",
    "exp": 1704067200,  // Short expiration (1 hour)
    "iat": 1704063600,
    "jti": "unique-token-id"  // For revocation
}

OAuth 2.0 Security

// PKCE for public clients (mobile, SPA)
// Prevents authorization code interception

// 1. Generate code_verifier (random string)
const codeVerifier = generateRandomString(128);

// 2. Create code_challenge
const codeChallenge = base64url(sha256(codeVerifier));

// 3. Authorization request includes challenge
/authorize?response_type=code&code_challenge=xxx&code_challenge_method=S256

// 4. Token exchange includes verifier
POST /token
code=xxx&code_verifier=original_verifier
Authentication Checklist
  • Use HTTPS for all endpoints
  • Implement rate limiting on auth endpoints
  • Use short-lived access tokens (15min-1hr)
  • Store refresh tokens securely (httpOnly cookies)
  • Implement token revocation
  • Use PKCE for OAuth public clients

4. Authorization Vulnerabilities

Broken Function Level Authorization

// User endpoint
GET /api/users/me

// Admin endpoint - accessible to regular users!
GET /api/admin/users
DELETE /api/admin/users/123

// Prevention: Middleware-based RBAC
const requireAdmin = (req, res, next) => {
    if (req.user.role !== 'admin') {
        return res.status(403).json({error: 'Admin required'});
    }
    next();
};

app.delete('/api/admin/users/:id', requireAdmin, deleteUser);

5. Input Validation & Injection

// NoSQL Injection (MongoDB)
POST /api/login
{"email": {"$gt": ""}, "password": {"$gt": ""}}
// Matches any user!

// Prevention: Validate input types
const { email, password } = req.body;
if (typeof email !== 'string' || typeof password !== 'string') {
    return res.status(400).json({error: 'Invalid input'});
}

// Use schema validation (Joi, Zod)
const schema = z.object({
    email: z.string().email(),
    password: z.string().min(8)
});
const validated = schema.parse(req.body);

6. Rate Limiting Strategies

// Express rate limiting
const rateLimit = require('express-rate-limit');

// General API limit
const apiLimiter = rateLimit({
    windowMs: 15 * 60 * 1000, // 15 minutes
    max: 100,
    message: {error: 'Too many requests'}
});

// Strict auth endpoint limit
const authLimiter = rateLimit({
    windowMs: 60 * 60 * 1000, // 1 hour
    max: 5, // 5 attempts per hour
    message: {error: 'Too many login attempts'}
});

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

7. GraphQL-Specific Security

GraphQL Vulnerabilities
// Introspection exposure
{__schema{types{name,fields{name}}}}

// Deeply nested queries (DoS)
query {
    users {
        friends {
            friends {
                friends { # 100 levels deep = server crash
                }
            }
        }
    }
}

// Batched queries for brute force
[
    {"query": "mutation{login(pass:\"a\"){token}}"},
    {"query": "mutation{login(pass:\"b\"){token}}"},
    // 10000 more...
]
GraphQL Protections
// Disable introspection in production
const server = new ApolloServer({
    introspection: process.env.NODE_ENV !== 'production'
});

// Query depth limiting
const depthLimit = require('graphql-depth-limit');
validationRules: [depthLimit(5)]

// Query complexity analysis
// Cost-based rate limiting

8. API Security Testing

Essential Tools

# BOLA testing with ffuf
ffuf -u https://api.target.com/users/FUZZ/profile \
     -w ids.txt -H "Authorization: Bearer TOKEN"

# Rate limit testing
for i in {1..1000}; do
    curl -s https://api.target.com/login -d "..." &
done

# JWT testing
python3 jwt_tool.py TOKEN -X a  # Algorithm confusion
python3 jwt_tool.py TOKEN -C -d passwords.txt  # Crack secret

FAQ

Should I use API keys or OAuth?
API keys for server-to-server communication. OAuth for user-facing applications where you need delegated authorization and token refresh capabilities.
Is GraphQL more secure than REST?
Neither is inherently more secure. GraphQL has unique risks (introspection, nested queries, batching) while REST has BOLA/IDOR. Both require proper security implementation.

OAuth Security GraphQL Guide OWASP Top 10