Part II: Vulnerability Study Chapter 11

API and Modern Application Security

REST API vulnerabilities, GraphQL attacks, OAuth/OIDC exploitation, JWT weaknesses, and modern application security

Chapter 11: API and Modern Application Security

The Optus API Disaster

In September 2022, Optusβ€”Australia’s second-largest telecommunications company with 10 million customersβ€”discovered a catastrophic data breach. Attackers had accessed personal data of 9.8 million customers, including names, dates of birth, addresses, phone numbers, and for some, passport and driver’s license numbers.

The attack vector? An unauthenticated API endpoint.

Optus had exposed a customer identity API to the internet without requiring authentication. The API accepted sequential customer identifiers, allowing attackers to iterate through records and extract personal data one customer at a time. There was no rate limiting, no anomaly detection, and no access logging that might have caught the enumeration in progress.

The breach cost Optus over $140 million AUD in immediate remediation, triggered Australia’s largest data breach investigation, led to regulatory reform requiring the telecommunications industry to fund new digital identity protections, and prompted the resignation of the company’s CEO Kelly Bayer Rosmarin.

A simple authentication checkβ€”standard API security practiceβ€”would have prevented the entire incident.

Modern applications expose APIs as their primary interfaceβ€”not just for web frontends, but for mobile apps, third-party integrations, and internal microservices. These APIs carry sensitive data and privileged operations, making them high-value targets. API security is no longer a specialtyβ€”it’s a fundamental skill for every security professional.


Why API Security Matters

The application architecture landscape has fundamentally shifted:

Traditional Web Application (2000s)

Traditional Web Application (2000s):
───────────────────────────────────

[Browser] ───HTTP───► [Web Server] ───► [App Logic] ───► [Database]
                           β”‚
                      Renders HTML
                           β”‚
              β—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Modern API-Driven Application (2020s):
─────────────────────────────────────

[Browser SPA] ──┐                    β”Œβ”€β”€β–Ί [Service A]
[Mobile App] ───┼── API Gateway ─────┼──► [Service B]
[Partner App] ───        β”‚           β”œβ”€β”€β–Ί [Service C]
[IoT Device] β”€β”€β”€β”˜        β”‚           └──► [Service D]
                         β”‚
                    [Auth Service]
                         β”‚
                    [Rate Limiter]

Why this matters for security:

TraditionalModern API-Driven
Single entry pointMultiple entry points
Server renders all outputClient interprets raw data
Sessions manage stateTokens manage state
Security through obscurity possibleAPIs documented and discoverable
Attacks require browser interactionAttacks can be fully automated

The OWASP API Security Top 10

The OWASP API Security Project identifies the most critical API security risks:

RiskDescriptionImpact
API1:2023Broken Object Level Authorization (BOLA)Unauthorized data access
API2:2023Broken AuthenticationAccount takeover
API3:2023Broken Object Property Level AuthorizationData exposure/manipulation
API4:2023Unrestricted Resource ConsumptionDoS, financial impact
API5:2023Broken Function Level AuthorizationPrivilege escalation
API6:2023Unrestricted Access to Sensitive Business FlowsBusiness logic abuse
API7:2023Server Side Request ForgeryInternal resource access
API8:2023Security MisconfigurationVarious
API9:2023Improper Inventory ManagementShadow API exposure
API10:2023Unsafe Consumption of APIsSupply chain attacks

We’ll explore the most critical of these with practical examples.


Broken Object Level Authorization (BOLA)

BOLA (formerly IDOR) is the #1 API security riskβ€”and it’s devastatingly simple.

The Vulnerability

APIs often expose endpoints that accept object identifiers:

GET /api/v1/users/12345/profile
GET /api/v1/orders/67890
GET /api/v1/documents/abc123

If the API doesn’t verify that the authenticated user has permission to access the specified object, attackers can access other users’ data by simply changing the ID.

Attack Example

Legitimate request

Legitimate request:
───────────────────
GET /api/v1/users/100/bank-details
Authorization: Bearer eyJ...user_100_token...

Response:
{
  "user_id": 100,
  "account_number": "****1234",
  "routing_number": "****5678"
}

BOLA attack:
────────────
GET /api/v1/users/101/bank-details    ← Changed ID
Authorization: Bearer eyJ...user_100_token...  ← Same token!

Vulnerable Response:
{
  "user_id": 101,
  "account_number": "****9999",      ← Different user's data!
  "routing_number": "****0000"
}

Attack Automation

#!/usr/bin/env python3
"""
BOLA Scanner - Educational demonstration
Only use against APIs you have authorization to test!
"""

import requests
import time

def scan_bola(base_url, endpoint_template, token, id_range):
    """
    Scan for BOLA vulnerability by iterating through object IDs.
    
    Args:
        base_url: API base URL
        endpoint_template: Endpoint with {id} placeholder
        token: Valid authentication token
        id_range: Range of IDs to test
    """
    
    headers = {
        'Authorization': f'Bearer {token}',
        'Content-Type': 'application/json'
    }
    
    accessible_objects = []
    
    for obj_id in id_range:
        url = base_url + endpoint_template.format(id=obj_id)
        
        try:
            response = requests.get(url, headers=headers)
            
            if response.status_code == 200:
                print(f"[+] Accessible: {url}")
                accessible_objects.append({
                    'id': obj_id,
                    'data': response.json()
                })
            elif response.status_code == 403:
                print(f"[-] Forbidden: {url}")
            elif response.status_code == 404:
                pass  # Object doesn't exist
            else:
                print(f"[?] Status {response.status_code}: {url}")
                
            time.sleep(0.5)  # Rate limiting
            
        except Exception as e:
            print(f"[!] Error: {e}")
    
    return accessible_objects

# Example usage (authorized testing only!)
# results = scan_bola(
#     "https://api.example.com",
#     "/api/v1/users/{id}/profile",
#     "your_token_here",
#     range(1, 1000)
# )

Detection

Network indicators:

  • Sequential or patterned object ID requests
  • Same authentication token accessing many different user resources
  • 200 responses for resources that should be 403

Log analysis:

# Look for users accessing resources outside their normal pattern
# Assuming you log user_id and requested_resource_owner_id

SELECT 
    request_user_id,
    resource_owner_id,
    COUNT(*) as access_count
FROM api_access_logs
WHERE request_user_id != resource_owner_id
GROUP BY request_user_id, resource_owner_id
HAVING COUNT(*) > 10
ORDER BY access_count DESC;

Defense

  1. Authorization checks at the data access layer:
# Bad - no authorization check
def get_user_profile(user_id):
    return db.query(User).filter(User.id == user_id).first()

# Good - verify ownership
def get_user_profile(user_id, requesting_user):
    user = db.query(User).filter(User.id == user_id).first()
    
    if user is None:
        raise NotFoundError()
    
    if user.id != requesting_user.id and not requesting_user.is_admin:
        raise ForbiddenError()
    
    return user
  1. Use unpredictable identifiers:
Bad:  /api/users/1, /api/users/2, /api/users/3
Good: /api/users/550e8400-e29b-41d4-a716-446655440000

UUIDs don’t prevent BOLA, but they make enumeration harder.

  1. Implement proper access control middleware:
@app.route('/api/users/<user_id>/profile')
@require_auth
@require_ownership_or_admin('user_id')  # Decorator checks authorization
def get_user_profile(user_id):
    return UserService.get_profile(user_id)

PRO TIP

When testing for BOLA, don’t just increment IDs. Try: different users’ IDs (if you have multiple test accounts), the ID 0 and 1 (often admin or system accounts), negative numbers, maximum integer values, and IDs from different object types (sometimes user ID 1 is also order ID 1).


Authentication Vulnerabilities

Modern APIs typically use token-based authentication rather than sessions. Each method has its own vulnerabilities.

JWT Vulnerabilities

JSON Web Tokens (JWTs) are the dominant API authentication mechanismβ€”and a rich source of vulnerabilities.

JWT Structure

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

[Header].[Payload].[Signature]

Header (base64):
{
  "alg": "HS256",    ← Algorithm
  "typ": "JWT"
}

Payload (base64):
{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022,
  "exp": 1516242622,
  "role": "user"      ← Claims attackers want to modify
}

Signature:
HMACSHA256(
  base64UrlEncode(header) + "." + base64UrlEncode(payload),
  secret
)

Algorithm Confusion Attack

Some JWT libraries allow the algorithm to be specified in the header. If the server accepts β€œnone” as an algorithm, you can forge tokens:

import base64
import json

def forge_jwt_none_algorithm(payload):
    """
    Create a JWT with alg: none (unsigned).
    Only works if server accepts 'none' algorithm.
    """
    
    header = {
        "alg": "none",
        "typ": "JWT"
    }
    
    header_b64 = base64.urlsafe_b64encode(
        json.dumps(header).encode()
    ).rstrip(b'=').decode()
    
    payload_b64 = base64.urlsafe_b64encode(
        json.dumps(payload).encode()
    ).rstrip(b'=').decode()
    
    # No signature needed with alg: none
    return f"{header_b64}.{payload_b64}."

# Forge admin token
malicious_payload = {
    "sub": "1234567890",
    "name": "Attacker",
    "role": "admin",  # Elevated privileges
    "iat": 1516239022,
    "exp": 9999999999
}

forged_token = forge_jwt_none_algorithm(malicious_payload)

RS256 to HS256 Algorithm Switch

A more sophisticated attack: if the server uses RS256 (asymmetric) but accepts HS256 (symmetric), you can sign tokens using the public key as the secret:

RS256 (intended):
- Server signs with private key
- Server verifies with public key
- Attacker doesn't have private key β†’ can't forge

HS256 attack:
- Attacker has public key (it's public!)
- Attacker signs with public key as HS256 secret
- If server accepts HS256, it verifies with public key as secret
- Verification succeeds β†’ forged token accepted!

Weak Secret Keys

import jwt
import itertools
import string

def crack_jwt_secret(token, wordlist):
    """
    Brute-force JWT secret from wordlist.
    Only for authorized security testing!
    """
    
    for secret in wordlist:
        try:
            jwt.decode(token, secret, algorithms=['HS256'])
            return secret
        except jwt.InvalidSignatureError:
            continue
    
    return None

# Common weak secrets to try:
weak_secrets = [
    'secret',
    'password',
    'jwt_secret',
    'changeme',
    'your-256-bit-secret',
    # ... many more
]

JWT Best Practices

# Secure JWT configuration

import jwt
from datetime import datetime, timedelta

SECRET_KEY = os.environ.get('JWT_SECRET')  # Strong, from environment
ALGORITHM = 'RS256'  # Asymmetric preferred

def create_token(user_id, role):
    payload = {
        'sub': str(user_id),
        'role': role,
        'iat': datetime.utcnow(),
        'exp': datetime.utcnow() + timedelta(hours=1),  # Short expiry
        'jti': str(uuid.uuid4())  # Unique token ID for revocation
    }
    
    return jwt.encode(payload, PRIVATE_KEY, algorithm=ALGORITHM)

def verify_token(token):
    try:
        payload = jwt.decode(
            token,
            PUBLIC_KEY,
            algorithms=[ALGORITHM],  # Explicit algorithm list
            options={
                'require': ['exp', 'iat', 'sub'],  # Required claims
                'verify_exp': True
            }
        )
        
        # Additional checks
        if is_token_revoked(payload['jti']):
            raise jwt.InvalidTokenError('Token revoked')
            
        return payload
        
    except jwt.ExpiredSignatureError:
        raise AuthenticationError('Token expired')
    except jwt.InvalidTokenError as e:
        raise AuthenticationError(f'Invalid token: {e}')

OAuth 2.0 Vulnerabilities

OAuth 2.0 is the standard for delegated authorizationβ€”allowing applications to access resources on behalf of users. Its complexity creates many attack opportunities.

OAuth Flow Overview

Authorization Code Flow

Authorization Code Flow:
───────────────────────

β”Œβ”€β”€β”€β”€β”€β”€β”                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚      β”‚  1. Redirect to authorize    β”‚              β”‚
β”‚ User β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β–Ίβ”‚ Auth Server  β”‚
β”‚      β”‚                              β”‚              β”‚
β””β”€β”€β”¬β”€β”€β”€β”˜                              β””β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜
   β”‚                                         β”‚
   β”‚  2. User authenticates & consents       β”‚
   β”‚                                         β”‚
   β”‚  3. Redirect with authorization code    β”‚
   β–Όβ—„β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
β”Œβ”€β”€β”€β”€β”€β”€β”
β”‚      β”‚  4. Exchange code for tokens
β”‚Clientβ”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ App  β”‚                                     β”‚
β””β”€β”€β”¬β”€β”€β”€β”˜                                     β–Ό
   β”‚                              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
   β”‚  6. Access API with token    β”‚              β”‚
   └─────────────────────────────►│ Resource API β”‚
                                  β”‚              β”‚
                                  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
        5. Returns access_token, refresh_token

Redirect URI Manipulation

The redirect_uri parameter is where the authorization server sends the user after authentication. If validation is weak, attackers can steal authorization codes:

Legitimate:
https://auth.example.com/authorize?
  client_id=app123&
  redirect_uri=https://app.example.com/callback&
  response_type=code&
  state=abc123

Attack - open redirect:
https://auth.example.com/authorize?
  client_id=app123&
  redirect_uri=https://app.example.com/callback/../../../attacker.com&
  response_type=code&
  state=abc123

Attack - subdomain bypass:
redirect_uri=https://evil.app.example.com/callback

Attack - parameter pollution:
redirect_uri=https://app.example.com/callback&redirect_uri=https://attacker.com

Authorization Code Interception

Without PKCE (Proof Key for Code Exchange), authorization codes can be intercepted:

Without PKCE

Without PKCE:
────────────

1. Attacker installs malicious app on victim's device
2. Malicious app registers same custom URL scheme as legitimate app
3. When auth server redirects with code, malicious app receives it
4. Malicious app exchanges code for tokens

With PKCE:
──────────

1. Client generates random code_verifier
2. Client sends hash of verifier (code_challenge) in auth request
3. After receiving code, client sends original code_verifier
4. Server verifies hash matches β†’ only original client can exchange code

PKCE Implementation

import hashlib
import base64
import secrets

def generate_pkce_pair():
    """Generate PKCE code_verifier and code_challenge."""
    
    # Generate random verifier (43-128 chars)
    code_verifier = base64.urlsafe_b64encode(
        secrets.token_bytes(32)
    ).rstrip(b'=').decode()
    
    # Create challenge (SHA256 hash of verifier)
    code_challenge = base64.urlsafe_b64encode(
        hashlib.sha256(code_verifier.encode()).digest()
    ).rstrip(b'=').decode()
    
    return code_verifier, code_challenge

# Authorization request includes code_challenge
# Token request includes code_verifier

OAuth Security Checklist

  • Always use PKCE for public clients
  • Validate redirect_uri exactly (no wildcards)
  • Use short-lived authorization codes (< 10 minutes)
  • Bind tokens to client
  • Implement state parameter to prevent CSRF
  • Use HTTPS everywhere
  • Limit scope to minimum necessary
  • Validate scopes on resource server, not just auth server

GraphQL Security

GraphQL offers a flexible query language for APIsβ€”and introduces unique security challenges.

GraphQL vs REST

REST GraphQL

REST:                              GraphQL:
─────                              ────────

GET /users/1                       query {
GET /users/1/posts                   user(id: 1) {
GET /users/1/posts/1/comments          name
                                      posts {
3 requests, fixed response shape       title
                                      comments {
                                        text
                                      }
                                    }
                                  }
                                }
                                
                                1 request, client defines shape

Introspection Disclosure

GraphQL schemas are often discoverable via introspection queries:

# Introspection query to dump entire schema
{
  __schema {
    types {
      name
      fields {
        name
        type {
          name
        }
      }
    }
  }
}

# Query to find all queries and mutations
{
  __schema {
    queryType {
      fields {
        name
        description
        args {
          name
          type { name }
        }
      }
    }
    mutationType {
      fields {
        name
        args {
          name
          type { name }
        }
      }
    }
  }
}

Denial of Service via Deep Queries

GraphQL allows nested queries that can overwhelm servers:

# Exponential complexity attack
query DosAttack {
  users {         # 100 users
    posts {       # Γ— 50 posts each
      comments {  # Γ— 100 comments each
        author {  # Γ— 1 author
          posts { # Γ— 50 posts
            comments {  # = 25,000,000 comments
              text
            }
          }
        }
      }
    }
  }
}

Batching Attacks

GraphQL often allows multiple operations in one request:

# Brute force via batching
mutation {
  login1: login(email: "user@example.com", password: "password1") { token }
  login2: login(email: "user@example.com", password: "password2") { token }
  login3: login(email: "user@example.com", password: "password3") { token }
  # ... 1000 more attempts in single request
}

GraphQL Defense

// Apollo Server with security configurations

const server = new ApolloServer({
  typeDefs,
  resolvers,
  
  // Disable introspection in production
  introspection: process.env.NODE_ENV !== 'production',
  
  // Query depth limiting
  validationRules: [
    depthLimit(5),  // Max nesting depth
    createComplexityLimitRule(1000),  // Max query complexity
  ],
  
  plugins: [
    // Rate limiting plugin
    ApolloServerPluginRateLimiting({
      windowMs: 60000,
      max: 100
    }),
    
    // Query logging for detection
    {
      requestDidStart() {
        return {
          didResolveOperation({ request, document }) {
            logQuery(request, getComplexity(document));
          }
        }
      }
    }
  ]
});

WebSocket Security

WebSockets enable bidirectional, real-time communicationβ€”with security implications often overlooked.

WebSocket Vulnerabilities

// Insecure WebSocket implementation

const ws = new WebSocket('ws://api.example.com/chat');

ws.onopen = () => {
  // No authentication!
  ws.send(JSON.stringify({
    type: 'message',
    room: 'admin-channel',  // Unauthorized room access
    content: 'Malicious message'
  }));
};

// Cross-Site WebSocket Hijacking (CSWSH)
// If Origin header isn't validated, attacker's page can connect
// using victim's cookies/session

WebSocket Defense

// Secure WebSocket server

const WebSocket = require('ws');
const jwt = require('jsonwebtoken');

const wss = new WebSocket.Server({ 
  port: 8080,
  verifyClient: (info, callback) => {
    // Validate Origin
    const allowedOrigins = ['https://app.example.com'];
    if (!allowedOrigins.includes(info.origin)) {
      callback(false, 403, 'Origin not allowed');
      return;
    }
    
    // Validate authentication token
    const token = info.req.headers['sec-websocket-protocol'];
    try {
      const decoded = jwt.verify(token, SECRET_KEY);
      info.req.user = decoded;
      callback(true);
    } catch (err) {
      callback(false, 401, 'Invalid token');
    }
  }
});

wss.on('connection', (ws, req) => {
  const user = req.user;
  
  ws.on('message', (message) => {
    const data = JSON.parse(message);
    
    // Authorize each action
    if (data.type === 'join_room') {
      if (!canUserAccessRoom(user, data.room)) {
        ws.send(JSON.stringify({ error: 'Unauthorized' }));
        return;
      }
    }
    
    // Input validation
    if (!isValidMessage(data)) {
      ws.close(1008, 'Invalid message format');
      return;
    }
    
    // Process message
    handleMessage(user, data);
  });
});

API Security Testing Methodology

Reconnaissance

# Discover API endpoints
# 1. Check documentation
curl https://api.example.com/docs
curl https://api.example.com/swagger.json
curl https://api.example.com/openapi.json

# 2. Check JavaScript files for endpoints
# Download SPA bundle and search for /api/ patterns

# 3. Wordlist-based discovery
gobuster dir -u https://api.example.com -w api-wordlist.txt

# 4. Check for GraphQL
curl https://api.example.com/graphql -d '{"query":"{__typename}"}'

Authentication Testing

# Test for weak JWT secrets
jwt-cracker <token>

# Test algorithm confusion
python3 jwt_tool.py <token> -X a  # Try all algorithms

# Test for none algorithm
python3 jwt_tool.py <token> -X n

# Test OAuth misconfiguration
# Check redirect_uri validation
# Test PKCE bypass
# Check scope escalation

Authorization Testing

# BOLA testing
# Get resource with user A's token
curl -H "Authorization: Bearer A_TOKEN" https://api.example.com/users/1/data

# Try accessing user B's resource
curl -H "Authorization: Bearer A_TOKEN" https://api.example.com/users/2/data

# Function-level authorization
# Try accessing admin endpoints with regular user
curl -H "Authorization: Bearer USER_TOKEN" https://api.example.com/admin/users

Rate Limiting Tests

import asyncio
import aiohttp

async def test_rate_limits(url, token, num_requests=1000):
    """Test API rate limiting."""
    
    async with aiohttp.ClientSession() as session:
        headers = {'Authorization': f'Bearer {token}'}
        
        async def make_request(i):
            async with session.get(url, headers=headers) as resp:
                return resp.status
        
        tasks = [make_request(i) for i in range(num_requests)]
        results = await asyncio.gather(*tasks)
        
        success = results.count(200)
        rate_limited = results.count(429)
        
        print(f"Success: {success}, Rate Limited: {rate_limited}")
        
        if rate_limited == 0:
            print("[!] No rate limiting detected!")

Defense Strategies

Defense in Depth for APIs

Layer 1 Edge Protection (WAF, API Gateway)

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Layer 1: Edge Protection (WAF, API Gateway)                     β”‚
β”‚ - Rate limiting, basic input validation, bot protection         β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 2: Authentication                                         β”‚
β”‚ - Strong token validation, OAuth security, session management   β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 3: Authorization                                          β”‚
β”‚ - Object-level, function-level, field-level access control      β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 4: Input Validation                                       β”‚
β”‚ - Schema validation, type checking, sanitization                β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 5: Business Logic                                         β”‚
β”‚ - Rate limiting sensitive operations, fraud detection           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Layer 6: Data Protection                                        β”‚
β”‚ - Encryption, field masking, audit logging                      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

API Security Checklist

  • Authentication on all endpoints (except truly public ones)
  • Authorization checks for every resource access
  • Rate limiting per user, IP, and globally
  • Input validation (schema-based)
  • Output encoding/escaping
  • TLS everywhere (minimum TLS 1.2)
  • Security headers (CORS, Content-Type, etc.)
  • Logging and monitoring
  • API versioning strategy
  • Deprecation process for old versions
  • Regular security testing

Key Takeaways

  1. APIs are the primary attack surface for modern applicationsβ€”understanding API security is essential

  2. BOLA (Broken Object Level Authorization) is the #1 API vulnerabilityβ€”always verify resource access permissions

  3. JWT security requires careful implementation: validate algorithms, use strong secrets, implement proper expiry

  4. OAuth complexity creates vulnerabilitiesβ€”always use PKCE, validate redirect URIs exactly

  5. GraphQL introduces unique risks: introspection disclosure, DoS via complex queries, batching attacks

  6. WebSockets need authentication and authorization for each message, not just the connection

  7. Defense in depth applies to APIs: multiple layers from edge protection to data protection


Review Questions

  1. What is BOLA, and why is it the #1 API security vulnerability?

  2. Describe the JWT β€œalgorithm confusion” attack and how to prevent it.

  3. Why is PKCE important for OAuth security, and how does it work?

  4. What unique security challenges does GraphQL introduce compared to REST?

  5. How would you test an API for authorization vulnerabilities?


Further Reading

  • OWASP API Security Top 10: owasp.org/API-Security
  • JWT.io Debugger: jwt.io
  • OAuth 2.0 Security Best Practices: tools.ietf.org/html/draft-ietf-oauth-security-topics
  • GraphQL Security: graphql.org/learn/authorization/