Executive Summary
APIs are the backbone of modern software. They power mobile applications, enable third-party integrations, and connect microservices. They are also the primary attack surface for data breaches, account takeover, and business logic exploitation.
In 2024, API attacks accounted for a disproportionate share of data breaches — not because APIs are inherently insecure, but because they are often deployed and evolved faster than security controls are applied. Developers build APIs to enable functionality; security teams audit them retrospectively, if at all.
This guide is written for developers, architects, and security engineers who build, secure, and test APIs. It covers the OWASP API Security Top 10, practical security controls for common API patterns, and a testing methodology for finding vulnerabilities before attackers do.
Chapter 1: The API Attack Surface
Before securing an API, understand what you are protecting.
API Types and Their Security Profiles
| API Type | Common Use | Security Considerations |
|---|---|---|
| REST (HTTP/JSON) | Web and mobile backends | IDOR, injection, broken auth |
| GraphQL | Flexible data queries | Introspection abuse, query depth attacks, broken auth |
| gRPC | Microservice communication | Protobuf injection, missing auth on internal services |
| WebSocket | Real-time applications | Persistent auth issues, message injection |
| SOAP (legacy) | Enterprise integrations | XML injection, XXE, replay attacks |
Enumeration: The Hidden Attack Surface
Most organisations do not have a complete inventory of their APIs. Common sources of unknown API exposure:
- Shadow APIs: Old API versions (
/api/v1/) never decommissioned while/api/v3/is actively maintained - Debug/admin endpoints: Developer convenience endpoints (
/debug,/admin/users,/internal/metrics) accidentally deployed to production - Third-party components: Libraries and frameworks that expose their own endpoints
- Microservice leakage: Internal service APIs exposed through misconfigured load balancers or service meshes
First step in API security: Build a complete inventory. Enumerate your own APIs the way an attacker would:
- Review all routes in your framework’s router configuration
- Search code repositories for API endpoint definitions
- Crawl your application with a tool like Burp Suite or Postman
- Monitor your API gateway for undocumented traffic patterns
Chapter 2: OWASP API Security Top 10
The OWASP API Security Top 10 (2023) defines the most critical API security risks.
API1: Broken Object Level Authorization (BOLA)
What it is: An API endpoint that accepts an object ID but does not verify whether the authenticated user is authorised to access that specific object.
Example:
GET /api/v1/orders/12345
Authorization: Bearer <user_A_token>
If the API returns order 12345 regardless of whether the authenticated user owns that order, it has BOLA. User A can iterate order IDs to access User B’s orders.
Fix: Every API endpoint that accesses an object must verify ownership or authorisation:
# WRONG
order = db.get_order(order_id)
return order
# CORRECT
order = db.get_order(order_id)
if order.user_id != request.user.id:
raise Forbidden("Access denied")
return order
BOLA is the #1 API vulnerability because it is easy to introduce and invisible in automated security scans — it requires business logic understanding to detect.
API2: Broken Authentication
What it is: Weak authentication mechanisms that allow attackers to impersonate users.
Common patterns:
- JWT tokens with
alg: noneaccepted - JWT secrets that are weak or default values
- No token expiry or very long expiry (30-day tokens)
- Tokens not invalidated on logout
- Predictable session tokens
Fix:
# JWT validation — verify algorithm explicitly
jwt.decode(
token,
secret_key,
algorithms=["HS256"], # NEVER use ["none"] or leave flexible
options={"verify_exp": True}
)
Implement token revocation: maintain a server-side denylist for logged-out tokens until they naturally expire.
API3: Broken Object Property Level Authorization (BOPLA)
What it is: Exposing or accepting more object properties than the user is authorised to access or modify.
Mass assignment example:
PATCH /api/v1/users/me
{
"email": "user@example.com",
"role": "admin" ← should not be writable by the user
}
If the API blindly applies all received properties to the object, an attacker can elevate their own privileges.
Excessive data exposure:
GET /api/v1/users/me
{
"id": 123,
"email": "user@example.com",
"password_hash": "$2b$12$...", ← should never be returned
"internal_credit_score": 720, ← should not be exposed
"role": "admin"
}
Fix:
- Use explicit serializers/schemas that define exactly which fields are readable and writable
- Never auto-serialise ORM objects — always use a DTO/serialiser with explicit field lists
- Separate read schemas from write schemas
API4: Unrestricted Resource Consumption
What it is: No limits on API request volume, payload size, or computational complexity — enabling DoS, account enumeration, and credential stuffing.
Fix:
- Rate limiting at the API gateway layer (per user, per IP, per endpoint)
- Request payload size limits (reject requests above a threshold)
- Pagination required for collection endpoints — no unbounded queries
- Query complexity limits for GraphQL
# NGINX rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
limit_req zone=api burst=20 nodelay;
API5: Broken Function Level Authorization
What it is: Admin or internal API functions accessible to regular users due to missing authorisation checks.
Common pattern: Administrative endpoints at predictable URLs (/admin/, /internal/, /manage/) that rely on obscurity rather than access control.
Fix:
- Implement RBAC (Role-Based Access Control) at the route level
- Use middleware/decorators to enforce role requirements:
@app.route('/admin/users')
@require_role(['admin', 'super_admin'])
def list_users():
...
- Audit all routes for authorisation decorators — any route without one is a finding
API6: Unrestricted Access to Sensitive Business Flows
What it is: High-value business flows (checkout, password reset, referral redemption) exposed without anti-abuse controls.
Example: A referral bonus endpoint with no per-account limit — attackers create thousands of fake accounts, refer each other, and withdraw bonuses.
Fix: Apply business logic controls:
- Limit critical actions per account per time period
- Require verified email/phone for sensitive operations
- Implement CAPTCHA on high-value unauthenticated flows
- Monitor for abnormal usage patterns
API7: Server-Side Request Forgery (SSRF)
What it is: API endpoints that fetch external URLs based on user input can be abused to make the server issue requests to internal systems.
Example:
POST /api/v1/thumbnail
{"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"}
On AWS, this fetches instance metadata including IAM credentials.
Fix:
- Validate and allowlist permitted URL destinations
- Block requests to RFC1918 ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16) and link-local addresses (169.254.0.0/16)
- Use IMDSv2 (token-required) on all EC2 instances
API8: Security Misconfiguration
What it is: Default configurations, verbose error messages, unnecessary features enabled.
Common API security misconfigurations:
- CORS configured with
Access-Control-Allow-Origin: *on authenticated endpoints - Stack traces returned in error responses (exposes framework, file paths, database schema)
- HTTP instead of HTTPS for API endpoints
- Default API gateway configurations without authentication
Fix:
- CORS: explicitly list permitted origins; never use wildcard for authenticated endpoints
- Error handling: return generic error messages to clients; log detailed errors server-side
- HTTPS everywhere: redirect HTTP to HTTPS; HSTS header enforced
API9: Improper Inventory Management
What it is: Attackers target undocumented, deprecated, or development API versions that lack security controls applied to current versions.
Fix:
- Maintain an API inventory (OpenAPI/Swagger spec for all exposed APIs)
- Decommission deprecated API versions — don’t just stop using them, remove them
- API gateway as a single point of entry — all traffic routed through the gateway, internal APIs not directly exposed
API10: Unsafe Consumption of APIs
What it is: Trusting data from third-party APIs without validation, leading to injection attacks from compromised or malicious upstream APIs.
Fix:
- Validate and sanitise all data received from third-party APIs before use
- Apply the same input validation rules to third-party API responses as to user input
- Monitor third-party API dependencies for security advisories
Chapter 3: Authentication and Authorisation
JWT Best Practices
JSON Web Tokens are the standard authentication mechanism for APIs. They are also frequently misconfigured.
# Token generation
payload = {
"sub": str(user.id),
"iat": datetime.utcnow(),
"exp": datetime.utcnow() + timedelta(minutes=15), # Short expiry
"jti": str(uuid4()) # Unique token ID for revocation
}
token = jwt.encode(payload, SECRET_KEY, algorithm="HS256")
JWT checklist:
- Token expiry ≤ 15 minutes for access tokens
- Refresh tokens with sliding expiry and rotation
- Token revocation supported (jti-based denylist or short expiry)
- Algorithm explicitly specified in validation — never
algorithms=None - Secret key minimum 256 bits (32 bytes), generated with cryptographic randomness
- Sensitive data not stored in JWT payload (it is base64-encoded, not encrypted)
OAuth 2.0 and API Security
For third-party integrations and delegated access, implement OAuth 2.0:
- Use PKCE (Proof Key for Code Exchange) for all public clients (mobile apps, SPAs)
- Use client credentials flow for machine-to-machine authentication
- Never use implicit flow (deprecated in OAuth 2.1)
- Scope tokens narrowly —
read:ordersnotread:* - Validate
redirect_uriexactly against a registered allowlist
API Key Management
For service-to-service or developer API access:
- Generate API keys with cryptographic randomness (minimum 32 bytes)
- Hash API keys before storage — never store plaintext
- Associate API keys with specific scopes and rate limits
- Implement key rotation without downtime (support multiple active keys per account)
- Provide audit logs: which key accessed what, when
Chapter 4: Input Validation and Injection Prevention
All data arriving at your API is untrusted. Validate it before use.
Validation Strategy
Schema validation at the boundary:
from pydantic import BaseModel, validator, constr
class CreateOrderRequest(BaseModel):
product_id: int
quantity: int
note: constr(max_length=500) | None = None
@validator('quantity')
def quantity_must_be_positive(cls, v):
if v <= 0:
raise ValueError('Quantity must be positive')
return v
SQL Injection Prevention
Parameterised queries, always:
# VULNERABLE
query = f"SELECT * FROM users WHERE email = '{email}'"
# SAFE — parameterised query
query = "SELECT * FROM users WHERE email = %s"
cursor.execute(query, (email,))
Even with ORMs, avoid raw query construction from user input:
# VULNERABLE — Django ORM raw query
User.objects.raw(f"SELECT * FROM users WHERE name = '{name}'")
# SAFE — ORM parameterisation
User.objects.filter(name=name)
Command Injection
Never pass user input to shell commands:
# VULNERABLE
subprocess.run(f"convert {filename} output.png", shell=True)
# SAFE
subprocess.run(["convert", filename, "output.png"], shell=False)
Chapter 5: Rate Limiting and Abuse Prevention
Rate Limiting Architecture
Implement rate limiting at multiple layers:
Layer 1 — API Gateway (coarse-grained):
- Global rate limits: maximum requests per second across all clients
- IP-based limiting: prevent single-IP flooding
Layer 2 — Application (fine-grained):
- Per-user limits: 100 requests/minute for standard, 1000 for premium
- Per-endpoint limits: authentication endpoints get tighter limits
- Per-resource limits: 5 password reset emails per account per hour
Headers to return:
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 47
X-RateLimit-Reset: 1716854400
Retry-After: 60 (when limit is exceeded)
Credential Stuffing Prevention
Authentication endpoints are primary targets for automated attacks:
- Rate limit by IP: maximum 5 failed attempts per IP per 10 minutes
- Rate limit by account: lock after 10 failures, require email verification to unlock
- Implement CAPTCHA after 3 consecutive failures from the same IP
- Monitor for credential stuffing signatures: many accounts, distributed IPs, rapid sequential attempts
- Consider device fingerprinting for high-risk endpoints
Chapter 6: GraphQL Security
GraphQL introduces unique security challenges not present in REST APIs.
Disable Introspection in Production
# Apollo Server
app = ApolloServer(schema=schema, introspection=False)
Introspection reveals your entire API schema — field names, types, relationships. It is a goldmine for attackers mapping your attack surface. Disable it in production; use tools like Apollo Studio for development exploration.
Query Depth and Complexity Limits
GraphQL allows deeply nested queries. Without limits, a single query can trigger thousands of database calls:
# This query could join many tables and return millions of rows
{
users {
orders {
items {
product {
reviews {
author {
orders { ... }
}
}
}
}
}
}
}
Fix:
from graphql import build_schema
from graphql.validation.rules import QueryDepthLimitRule
DEPTH_LIMIT = 5
COMPLEXITY_LIMIT = 100
Batching Attacks
GraphQL supports batching multiple queries in a single request. Attackers use this to bypass rate limits:
[
{"query": "mutation { login(email: 'user1@...', password: 'pass1') }"},
{"query": "mutation { login(email: 'user2@...', password: 'pass2') }"},
... (1000 queries)
]
Fix: Limit batch size, or disable batching on sensitive mutations.
Chapter 7: API Security Testing Methodology
Phase 1: Discovery and Mapping
Before testing, map the complete API surface:
- Import OpenAPI/Swagger spec into Burp Suite or Postman
- Spider the application to discover undocumented endpoints
- Review JavaScript bundles for API calls (endpoint discovery)
- Check for
robots.txt,.well-known/, and documentation endpoints
Phase 2: Authentication Testing
- Attempt access without authentication tokens
- Test with expired tokens
- Test with tokens belonging to other users
- Attempt JWT algorithm confusion attacks
- Test token invalidation on logout
Phase 3: Authorisation Testing (BOLA/BOPLA)
The most impactful testing phase:
- Create two test accounts (User A and User B)
- Perform all actions as User A, noting all object IDs returned
- Switch to User B’s authentication token
- Attempt to access User A’s objects using the captured IDs
- Attempt to modify User A’s data
Test every parameter that accepts an identifier: IDs in path parameters, query parameters, and request bodies.
Phase 4: Input Validation
- Test all string parameters for SQLi:
','',1 OR 1=1,1; DROP TABLE - Test for NoSQL injection:
{"$gt": ""},{"$where": "sleep(5000)"} - Test for command injection:
; ls,| whoami,`id` - Test file upload endpoints: upload PHP/JSP files disguised as images
- Test for SSRF: submit internal IP addresses and cloud metadata URLs
Phase 5: Business Logic
This requires understanding the application:
- Negative values in financial fields
- Workflow bypass (skip payment step, go directly to “payment confirmed” endpoint)
- Mass assignment: add extra fields to request bodies
- Race conditions: concurrent requests on single-use resources
Tools
| Purpose | Tool |
|---|---|
| Proxy / intercept | Burp Suite Professional |
| API scanning | Burp Suite Scanner, OWASP ZAP |
| Fuzzing | Burp Intruder, ffuf, wfuzz |
| JWT testing | jwt_tool |
| GraphQL testing | InQL, clairvoyance |
| Automated DAST | Nuclei (API templates) |
| Load testing / rate limit testing | locust, k6 |
Conclusion: Building a Secure API Programme
API security is not a one-time assessment — it is a continuous process integrated into development and operations.
Shift left: Integrate security into API development:
- OpenAPI spec review as part of design phase
- OWASP API Security Top 10 training for development teams
- SAST tools integrated in CI/CD pipelines
- Pre-production DAST scanning before every release
Ongoing testing:
- Annual API penetration test by external specialists
- Continuous DAST in staging environment
- Bug bounty programme for responsible disclosure
Runtime protection:
- API gateway with rate limiting and authentication enforcement
- WAF with API-specific rulesets
- API security monitoring (anomaly detection on access patterns)
- Runtime application self-protection (RASP) for high-risk applications
CyberneticsPlus specialises in API security testing and programme development. Our OWASP-aligned API penetration testing methodology covers REST, GraphQL, and gRPC APIs. Contact us to assess your API security posture.