Listen instead
Learning Objectives
- ✓ Explain why API security requires dedicated attention beyond traditional web application security
- ✓ Identify and mitigate all 10 OWASP API Security Top 10 (2023) vulnerability categories
- ✓ Implement secure API authentication using OAuth 2.0, JWT, and mTLS
- ✓ Apply authorization patterns (RBAC, ABAC, scope-based) consistently across API endpoints
- ✓ Secure GraphQL and gRPC APIs against their specific attack vectors
- ✓ Recognize AI-generated API code risks and MCP security vulnerabilities
- ✓ Design and execute comprehensive API security testing
1. Why Dedicated API Security Matters
APIs have fundamentally changed the attack surface of modern applications. Traditional web application security focuses on server-rendered pages, form submissions, and session-based authentication. API security addresses a different reality.
APIs Are the Primary Attack Surface
- 83% of web traffic is now API traffic (Cloudflare 2025)
- The average enterprise exposes 15,000+ API endpoints (Salt Security 2025)
- API attacks increased 681% between 2021 and 2024 (Salt Security)
- 74% of organizations experienced at least one API security incident in 2024
APIs Differ From Traditional Web Applications
| Characteristic | Traditional Web App | API |
|---|---|---|
| Client | Browser (known, constrained) | Any HTTP client (unknown, unconstrained) |
| Authentication | Session cookies | Tokens (JWT, API keys, OAuth) |
| Input | HTML forms (structured) | Arbitrary JSON/XML/binary payloads |
| Output | HTML pages | Structured data (JSON, Protobuf, GraphQL) |
| State | Server-side sessions | Often stateless (token-based) |
| Consumers | Humans | Machines, mobile apps, other services |
| Rate of change | Pages change infrequently | Endpoints change with every sprint |
| Discovery | Crawlable | Requires documentation or specification |
These differences mean that web application security tools and practices are necessary but insufficient for API security. APIs require dedicated security controls, testing approaches, and monitoring.
The Machine-to-Machine Problem
When a human uses a web application, there are natural rate limits (typing speed, click speed) and behavioral patterns that anomaly detection can leverage. When machines consume APIs, traffic volumes are orders of magnitude higher, patterns are algorithmic, and attacks are automated from the first request. APIs must be secured against automated, high-volume, sophisticated attacks from the first moment of deployment.
2. OWASP API Security Top 10 (2023)
The OWASP API Security Top 10 represents the most critical security risks specific to APIs. Each entry requires specific mitigations beyond generic web security practices.
Figure: OWASP API Security Top 10 (2023) — The most critical security risks specific to APIs
API1:2023 — Broken Object Level Authorization (BOLA)
Description: The API endpoint receives an identifier of an object (ID, slug, path) and does not verify that the requesting user is authorized to access that specific object. An attacker changes the identifier to access another user’s data.
Example:
GET /api/v1/invoices/12345
Authorization: Bearer <token_for_user_A>
# Attacker changes the ID:
GET /api/v1/invoices/12346 ← Returns user_B's invoice
Why it is #1: BOLA is the most prevalent API vulnerability because:
- It is easy to exploit (change one parameter)
- It is hard to detect with automated tools (requires understanding of object ownership)
- Developers often forget to add object-level checks after writing query-level auth
Mitigations:
- Implement authorization checks on every data access, verifying the requesting user owns or has permission to access the specific object
- Use random, non-sequential identifiers (UUIDs) to prevent enumeration — but this is defense-in-depth, NOT a replacement for authorization
- Centralize authorization logic in middleware or a shared service rather than implementing it per-endpoint
- Write integration tests that specifically verify cross-user access is denied
- Log and alert on object access patterns that indicate enumeration (sequential ID scanning from a single user)
# VULNERABLE
@app.get("/api/v1/invoices/{invoice_id}")
async def get_invoice(invoice_id: int, user: AuthenticatedUser):
invoice = await Invoice.get(invoice_id) # No ownership check
return invoice
# SECURE
@app.get("/api/v1/invoices/{invoice_id}")
async def get_invoice(invoice_id: UUID, user: AuthenticatedUser):
invoice = await Invoice.get(invoice_id)
if not invoice:
raise HTTPException(404)
if invoice.owner_id != user.id and not user.has_permission("invoices:read:all"):
raise HTTPException(403) # Explicit authorization check
return invoice
API2:2023 — Broken Authentication
Description: Authentication mechanisms are incorrectly implemented, allowing attackers to compromise authentication tokens, exploit implementation flaws, or assume other users’ identities.
Common manifestations:
- Accepting unsigned or weakly signed JWTs
- Not validating JWT expiration (
expclaim) - Using API keys in URLs (logged, cached, visible in browser history)
- Permitting brute-force attacks on authentication endpoints
- Returning different error messages for “user not found” vs. “wrong password”
- Not invalidating tokens after password change
- Accepting tokens with
alg: none
Mitigations:
- Use established authentication frameworks — never build custom auth (Module 3.3)
- Validate ALL JWT claims: signature, expiration, issuer, audience, not-before
- Implement rate limiting on authentication endpoints (5 attempts per minute per IP)
- Use strong, asymmetric JWT signing (RS256 or EdDSA), not symmetric (HS256) in multi-service environments
- Invalidate all existing tokens when credentials change
- Implement token rotation with short-lived access tokens (15 minutes) and longer-lived refresh tokens (hours/days)
- Never accept
alg: none— always enforce algorithm explicitly in verification
API3:2023 — Broken Object Property Level Authorization
Description: The API allows users to access or modify object properties they should not have access to. This includes mass assignment (setting properties not intended to be user-writable) and excessive data exposure (returning properties not needed by the client).
Excessive data exposure example:
// API returns ALL user fields, including internal ones
GET /api/v1/users/me
{
"id": "uuid-123",
"name": "Alice",
"email": "alice@example.com",
"password_hash": "$argon2id$...", // SHOULD NOT BE RETURNED
"is_admin": false, // SHOULD NOT BE RETURNED
"internal_notes": "VIP customer", // SHOULD NOT BE RETURNED
"api_key": "sk-..." // SHOULD NOT BE RETURNED
}
Mass assignment example:
// User sends properties they should not be able to set
PUT /api/v1/users/me
{
"name": "Alice Updated",
"is_admin": true, // User should not be able to set this
"subscription_tier": "enterprise" // User should not be able to set this
}
Mitigations:
- Use explicit response schemas that whitelist returned fields — never serialize entire database objects
- Use explicit input schemas with allowed fields — never bind request bodies directly to models
- Different response schemas for different roles (admin sees more fields than regular users)
- Validate that property-level access aligns with the user’s permissions
API4:2023 — Unrestricted Resource Consumption
Description: The API does not limit the resources a single client can consume. This enables denial-of-service attacks, excessive billing, and resource exhaustion.
Attack vectors:
- Sending extremely large request payloads
- Requesting extremely large result sets (no pagination limits)
- Flooding the API with requests (no rate limiting)
- Triggering expensive operations repeatedly (report generation, file processing)
- GraphQL query depth/complexity abuse
- Batch endpoint abuse (requesting thousands of operations in a single call)
Mitigations:
- Rate limiting: Requests per second/minute/hour per client, with graduated responses (429 Too Many Requests)
- Payload size limits: Maximum request body size enforced at web server and application layers
- Pagination enforcement: Maximum page size, cursor-based pagination preferred
- Timeout enforcement: Request processing timeouts at API gateway and application levels
- Resource quotas: Per-client limits on expensive operations (reports/day, uploads/hour)
- Cost-based rate limiting: Weight expensive operations higher in rate limit calculations
API5:2023 — Broken Function Level Authorization (BFLA)
Description: The API does not properly verify that the requesting user is authorized to invoke a specific function or operation. Differs from BOLA (which is about object access) — BFLA is about whether the user can invoke the operation at all.
Example:
# Regular user endpoint
GET /api/v1/users/me
→ 200 OK
# Admin endpoint — same authentication, no additional authorization
DELETE /api/v1/users/other-user-id
→ 200 OK ← Regular user should not be able to call admin endpoints
Mitigations:
- Deny by default: all endpoints require explicit authorization configuration
- Separate admin endpoints into different API routes with middleware enforcement
- Use role-based or scope-based access control checked at the gateway or middleware level
- Regularly audit endpoint-to-role mappings
- Test authorization with automated tests that use different role tokens
API6:2023 — Unrestricted Access to Sensitive Business Flows
Description: The API exposes business flows that are intended for human use (purchasing, posting content, reserving inventory) without controls that prevent automated abuse.
Examples:
- Bot-driven ticket scalping through the purchase API
- Automated coupon abuse through the cart API
- Spam content creation through the posting API
- Price scraping through the product listing API
- Automated account creation to inflate metrics
Mitigations:
- Device fingerprinting for sensitive flows
- CAPTCHA/challenge-response on critical business operations
- Behavioral analysis to detect bot patterns
- Business logic rate limiting (max purchases per user per time window)
- Require human-initiated token (CAPTCHA completion, SMS verification) for high-value operations
API7:2023 — Server-Side Request Forgery (SSRF)
Description: The API accepts a URL or network address from the client and makes a request to that address without validation. An attacker provides internal network addresses to access internal services, cloud metadata endpoints, or other resources not intended to be externally accessible.
Classic attack:
POST /api/v1/webhooks
{
"url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
}
// API fetches the URL and returns AWS IAM credentials
Mitigations:
- Validate and sanitize all client-supplied URLs
- Use allowlists for permitted URL schemes (https only), hosts, and ports
- Block requests to internal IP ranges (10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16, 169.254.169.254, localhost)
- Use a dedicated network-isolated service for outbound requests
- Disable HTTP redirects in outbound request clients (attackers use redirects to bypass allowlists)
- Do not return raw responses from fetched URLs — extract only the needed data
API8:2023 — Security Misconfiguration
Description: The API or its supporting infrastructure has insecure default configurations, missing security headers, unnecessary features enabled, or overly permissive settings.
Common misconfigurations:
- CORS set to
*(any origin) on authenticated endpoints - Missing security headers (Content-Security-Policy, X-Content-Type-Options, X-Frame-Options)
- Debug/trace endpoints enabled in production
- Default credentials on API management portals
- Unnecessary HTTP methods enabled (PUT, DELETE on read-only endpoints)
- Verbose error messages exposing internals
- TLS misconfiguration (weak ciphers, expired certificates)
Mitigations:
- Automated configuration scanning in CI/CD
- Security header enforcement via API gateway
- Environment-specific configuration management (dev configs never reach production)
- Regular configuration audits against CIS benchmarks
- Disable all unnecessary HTTP methods per-endpoint
- CORS configuration with explicit allowed origins, never wildcard for authenticated APIs
API9:2023 — Improper Inventory Management
Description: The organization does not maintain an accurate inventory of its APIs, leading to: unpatched old API versions, forgotten test/development endpoints exposed to production, undocumented shadow APIs, and unused endpoints that remain accessible.
Risks:
- Old API versions (v1, v2) remain accessible with known vulnerabilities while v3 is current
- Beta/test endpoints deployed to production without security controls
- Internal APIs exposed on external networks due to misconfigured routing
- Undocumented endpoints created by developers for debugging, never removed
- Third-party API integrations not tracked or monitored
Mitigations:
- Maintain a centralized API registry (API catalog)
- Require OpenAPI/Swagger specifications for all APIs
- Automate API discovery through traffic analysis and runtime scanning
- Enforce API versioning policy with mandatory deprecation timelines
- Separate internal and external API networks with strict routing controls
- Regular API inventory audits (quarterly minimum)
API10:2023 — Unsafe Consumption of APIs
Description: The application trusts data received from third-party APIs without validation, treating external API responses as trusted input. If the third-party API is compromised or returns malicious data, the consuming application is also compromised.
Example: An application consumes a currency exchange rate API. An attacker compromises the API and returns a response containing XSS payloads in the currency name field. The consuming application renders the currency name without encoding, executing the payload.
Mitigations:
- Validate all data received from external APIs against expected schemas
- Apply input validation and output encoding to external API data, just as you would for user input
- Use TLS with certificate validation for all external API connections
- Implement timeout and circuit breaker patterns for external API calls
- Monitor external API behavior for anomalies (unexpected data formats, sizes, or values)
- Have fallback behavior when external APIs return invalid data
3. API Authentication Patterns
API Keys
When to use: Service-to-service authentication with simple identity requirements, rate limiting by client, and analytics tracking.
Security properties:
- API keys authenticate the application, not the user
- They are bearer tokens — anyone with the key has access
- They cannot be scoped to specific users or operations (usually)
- They are long-lived and difficult to rotate without disruption
Best practices:
- Transmit in headers (
X-API-KeyorAuthorization), never in URLs - Store hashed (not plaintext) on the server side
- Implement key rotation with overlap periods (old key valid for 24 hours after new key issued)
- Scope keys by environment (separate keys for production, staging, development)
- Monitor and alert on unusual key usage patterns
- Rate limit per key
OAuth 2.0 Flows
Authorization Code Flow (with PKCE): Standard for web and mobile applications where a user grants permission.
User → App → Authorization Server (login page)
← Authorization Code
App → Authorization Server (code + PKCE verifier)
← Access Token + Refresh Token
App → Resource Server (Access Token)
← Protected Resource
Client Credentials Flow: Service-to-service authentication where no user is involved.
Service A → Authorization Server (client_id + client_secret)
← Access Token
Service A → Service B (Access Token)
← Protected Resource
Security requirements:
- Always use PKCE (Proof Key for Code Exchange) with Authorization Code flow — prevents code interception
- Use short-lived access tokens (5-15 minutes)
- Store refresh tokens securely (encrypted at rest, HttpOnly cookies for web apps)
- Validate the
redirect_uriexactly (no open redirector via partial matching) - Implement token revocation for compromised tokens
- Use the
stateparameter to prevent CSRF - Never use Implicit flow (deprecated due to token exposure in URL fragment)
JWT (JSON Web Tokens)
Claims to validate:
| Claim | Validation | Risk if Missing |
|---|---|---|
iss (issuer) | Must match expected issuer | Token from wrong authority accepted |
aud (audience) | Must match this service’s identifier | Token intended for another service accepted |
exp (expiration) | Must be in the future | Expired tokens accepted |
nbf (not before) | Must be in the past | Tokens used before intended time |
sub (subject) | Must be a valid user/service | Authorization applied to wrong entity |
iat (issued at) | Must be reasonable | Replay of ancient tokens |
Signing:
- Use asymmetric algorithms (RS256, EdDSA) in multi-service environments — only the authorization server has the private key, all services verify with the public key
- Use HS256 only in single-service environments where the same service issues and verifies tokens
- Never accept
alg: none - Explicitly set the allowed algorithm in verification — do not let the token’s header dictate the algorithm
Rotation:
- Rotate signing keys regularly (every 90 days)
- Use JWKS (JSON Web Key Set) endpoints for key distribution
- Support multiple active keys during rotation overlap
- Remove old keys from JWKS after all tokens signed with them have expired
mTLS (Mutual TLS)
When to use: High-security service-to-service communication, zero-trust architectures, financial/healthcare APIs.
How it works: Both client and server present certificates during the TLS handshake. The server verifies the client’s certificate against a trusted CA, and the client verifies the server’s certificate. This provides strong mutual authentication without bearer tokens.
Implementation:
- Issue client certificates from an internal CA (not a public CA)
- Implement certificate revocation checking (CRL or OCSP)
- Automate certificate rotation with short lifetimes (hours/days, not months)
- Use certificate-bound access tokens when combining mTLS with OAuth
4. API Authorization
RBAC (Role-Based Access Control)
Users are assigned roles, and roles have permissions. Simple and effective for most applications.
{
"roles": {
"viewer": ["read:invoices", "read:reports"],
"editor": ["read:invoices", "write:invoices", "read:reports"],
"admin": ["read:invoices", "write:invoices", "delete:invoices",
"read:reports", "write:reports", "manage:users"]
}
}
Implementation: Check the user’s role-derived permissions at the middleware or service layer on every request.
ABAC (Attribute-Based Access Control)
Access decisions based on attributes of the user, resource, action, and environment. More flexible than RBAC but more complex.
ALLOW if:
user.department == resource.department
AND user.clearance_level >= resource.classification
AND action IN ["read", "list"]
AND environment.time_of_day BETWEEN "08:00" AND "18:00"
Use when: RBAC is insufficient — when access depends on data attributes, not just user roles.
Scope-Based Access Control
OAuth 2.0 scopes limit what an access token can do, independent of the user’s full permissions. The token may have scope read:invoices even though the user has write:invoices permission.
Token scopes: ["read:invoices", "read:reports"]
User permissions: ["read:invoices", "write:invoices", "read:reports", "write:reports"]
Effective permissions = intersection = ["read:invoices", "read:reports"]
Always check both the token’s scopes AND the user’s permissions. Scopes constrain; user permissions authorize.
5. Input Validation for APIs
Schema Validation
Validate every API request against a schema before processing.
OpenAPI/JSON Schema validation:
# OpenAPI specification
paths:
/api/v1/invoices:
post:
requestBody:
required: true
content:
application/json:
schema:
type: object
required: [customer_id, amount, currency]
properties:
customer_id:
type: string
format: uuid
amount:
type: number
minimum: 0.01
maximum: 1000000
currency:
type: string
enum: [USD, EUR, GBP, JPY]
description:
type: string
maxLength: 500
additionalProperties: false # Reject unexpected fields
Implementation: Use schema validation middleware that automatically rejects non-conforming requests:
- Node.js:
express-openapi-validator,ajv - Python:
connexion(Flask),fastapi(Pydantic built-in) - Java:
spring-boot-starter-validation,swagger-request-validator - Go:
kin-openapi
Content-Type Enforcement
- Reject requests with unexpected Content-Type headers
- If the API accepts JSON, reject XML, form-encoded, and other content types
- Validate that the request body matches the declared Content-Type
- Do not rely on content-type auto-detection
Payload Size Limits
Enforce at multiple layers:
Web Server (nginx): client_max_body_size 10m;
API Gateway: Max payload 5MB
Application: Per-endpoint limits (e.g., file upload: 50MB, JSON body: 1MB)
Field-level: maxLength on string fields, maximum on numeric fields
6. Rate Limiting and Throttling
Strategies
Fixed window: Count requests per time window (e.g., 100 requests per minute). Simple but susceptible to burst at window boundaries.
Sliding window: Track request timestamps and count within a sliding time range. Smoother but more resource-intensive.
Token bucket: Each client has a bucket of tokens. Each request consumes a token. Tokens are replenished at a fixed rate. Allows bursts up to bucket capacity.
Leaky bucket: Requests enter a queue (bucket) and are processed at a fixed rate. Excess requests overflow (are rejected). Provides consistent processing rate.
Implementation
# Redis-based sliding window rate limiter
from redis import Redis
import time
redis = Redis()
def rate_limit(client_id: str, max_requests: int, window_seconds: int) -> bool:
key = f"ratelimit:{client_id}"
now = time.time()
pipe = redis.pipeline()
pipe.zremrangebyscore(key, 0, now - window_seconds) # Remove expired entries
pipe.zadd(key, {str(now): now}) # Add current request
pipe.zcard(key) # Count requests in window
pipe.expire(key, window_seconds) # Set key TTL
results = pipe.execute()
request_count = results[2]
return request_count <= max_requests
Distributed Rate Limiting
In a multi-instance deployment, rate limiting must be coordinated:
- Centralized store: Redis, Memcached — all instances share the same counter
- Distributed algorithm: Use a consistent hashing ring to route clients to specific instances
- API gateway enforcement: Centralize rate limiting at the gateway (Kong, AWS API Gateway, Envoy)
Response Codes
- 429 Too Many Requests: Include
Retry-Afterheader with seconds until the client can retry - 503 Service Unavailable: When global rate limits are exceeded (not client-specific)
- Never return 200 with an error body when rate limiting — use appropriate HTTP status codes
7. API Versioning and Deprecation Security
Versioning Strategies
| Strategy | Example | Security Consideration |
|---|---|---|
| URL path | /api/v1/users | Old versions remain accessible; must be patched or removed |
| Query parameter | /api/users?version=1 | Easy to manipulate; less visible in access logs |
| Header | Accept: application/vnd.api.v1+json | Not visible in URL-based monitoring tools |
| Content negotiation | Accept: application/json; version=1 | Complex; error-prone |
Security implications:
- Every active API version is a separate attack surface that must be maintained
- Vulnerabilities patched in v3 must also be patched in active v1 and v2
- Deprecated versions that remain accessible are high-risk targets
- API version inventory is required (OWASP API9)
Deprecation security process:
- Announce deprecation with timeline (6 months minimum for production APIs)
- Monitor deprecated version traffic to identify remaining consumers
- Return
Deprecationheader on all responses:Deprecation: true - Implement sunset date enforcement: after the deadline, return 410 Gone
- Remove the code and infrastructure for the deprecated version
8. GraphQL Security
GraphQL introduces unique security challenges due to its flexible query language.
Query Depth Limiting
GraphQL allows nested queries that can be used for denial-of-service:
# Malicious deeply nested query
query {
user(id: "1") {
friends {
friends {
friends {
friends {
friends {
# ... unlimited depth
}
}
}
}
}
}
}
Mitigation: Enforce a maximum query depth (typically 7-10 levels).
Query Complexity Analysis
Assign cost values to fields and enforce a maximum total cost per query:
user: cost 1
user.friends: cost 10 (list)
user.friends.posts: cost 50 (nested list)
Maximum query cost: 1000
Introspection Controls
GraphQL introspection queries (__schema, __type) reveal the entire API schema. In production:
- Disable introspection entirely, or
- Restrict introspection to authenticated admin users
- Never expose introspection on public endpoints
Batching Attacks
GraphQL allows multiple operations in a single request. Without limits, an attacker sends thousands of mutations in one request, bypassing per-request rate limiting:
mutation {
a1: createUser(input: {...})
a2: createUser(input: {...})
# ... 10,000 more
}
Mitigation: Limit the number of operations per request (maximum 10-20 operations).
Authorization in GraphQL
GraphQL resolvers must enforce authorization at the field level, not just the query level. A user authorized to see their own profile should not be able to resolve user.passwordHash or user.internalNotes through the graph.
9. gRPC Security
gRPC uses Protocol Buffers over HTTP/2. Its security considerations differ from REST APIs.
TLS
gRPC should always use TLS. Unlike REST APIs where TLS termination at a load balancer is common, gRPC benefits from end-to-end TLS because:
- HTTP/2 multiplexing means a single connection carries many concurrent requests
- Compromising the connection compromises all concurrent requests
- gRPC metadata (analogous to HTTP headers) may contain authentication tokens
Authentication Interceptors
gRPC interceptors (middleware equivalent) enforce authentication on every RPC call:
func AuthInterceptor(
ctx context.Context,
req interface{},
info *grpc.UnaryServerInfo,
handler grpc.UnaryHandler,
) (interface{}, error) {
md, ok := metadata.FromIncomingContext(ctx)
if !ok {
return nil, status.Error(codes.Unauthenticated, "missing metadata")
}
token := md.Get("authorization")
if len(token) == 0 {
return nil, status.Error(codes.Unauthenticated, "missing token")
}
claims, err := validateToken(token[0])
if err != nil {
return nil, status.Error(codes.Unauthenticated, "invalid token")
}
ctx = context.WithValue(ctx, "claims", claims)
return handler(ctx, req)
}
Input Validation
Protocol Buffers provide type safety but not business logic validation. A field defined as int32 prevents string injection but does not prevent negative values or business-invalid ranges.
- Use
protoc-gen-validatefor declarative validation rules in.protofiles - Validate business rules in service implementations
- Do not trust that proto schema enforcement is sufficient for security
Message Size Limits
gRPC has default maximum message sizes (4MB). For production:
- Set explicit limits per-service based on expected payload sizes
- Implement streaming for large data transfers instead of single large messages
- Monitor for clients sending near-limit-size messages as potential DoS indicators
10. API Gateway Security
API gateways provide centralized security enforcement for all backend APIs.
Centralized Authentication
The gateway authenticates all incoming requests before forwarding to backend services. Backend services receive pre-authenticated requests with verified identity claims.
Benefits:
- Single point of authentication logic maintenance
- Backend services focus on business logic
- Consistent authentication across all APIs
- Simplified key/token management
Implementation:
Client → API Gateway (verify JWT, check scopes) → Backend Service (trust gateway headers)
The gateway adds trusted headers (X-User-ID, X-User-Roles) and the backend trusts these headers because only the gateway can reach backend services (network policy enforcement).
Rate Limiting at the Gateway
Enforce rate limits before requests reach backend services:
- Global rate limits (total requests per second across all clients)
- Per-client rate limits (requests per second per API key/token)
- Per-endpoint rate limits (expensive endpoints get lower limits)
- Graduated enforcement (warn → throttle → block)
WAF Integration
Web Application Firewalls at the gateway layer provide:
- SQL injection detection in API parameters
- XSS payload detection
- Request anomaly detection
- Bot detection and mitigation
- IP reputation filtering
- Geo-blocking
Request/Response Transformation
The gateway can enforce security by transforming requests and responses:
- Strip sensitive headers from responses (Server, X-Powered-By)
- Add security headers to all responses (Content-Security-Policy, X-Content-Type-Options)
- Mask sensitive data in responses based on client permissions
- Validate request schemas before forwarding
11. AI-Generated API Code Risks
AI coding tools generate API code with characteristic vulnerability patterns that developers must recognize.
Missing Authorization Checks (BOLA/BFLA)
This is the most common vulnerability in AI-generated API code. AI generates functional endpoints that authenticate users but do not verify authorization at the object or function level.
# AI-GENERATED — TYPICAL OUTPUT
@app.get("/api/v1/orders/{order_id}")
async def get_order(order_id: int, current_user: User = Depends(get_current_user)):
order = await Order.get(order_id)
if not order:
raise HTTPException(404)
return order # MISSING: Does current_user own this order?
# WHAT IT SHOULD BE
@app.get("/api/v1/orders/{order_id}")
async def get_order(order_id: UUID, current_user: User = Depends(get_current_user)):
order = await Order.get(order_id)
if not order:
raise HTTPException(404)
if order.user_id != current_user.id and not current_user.has_role("admin"):
raise HTTPException(403)
return OrderResponse.from_orm(order) # Also using response schema to limit fields
Over-Permissive CORS Configurations
AI frequently generates CORS middleware with wildcard origins:
# AI-GENERATED — INSECURE
from fastapi.middleware.cors import CORSMiddleware
app.add_middleware(
CORSMiddleware,
allow_origins=["*"], # Any origin can make authenticated requests
allow_credentials=True, # Cookies are sent — combined with *, this is dangerous
allow_methods=["*"],
allow_headers=["*"],
)
# SECURE
app.add_middleware(
CORSMiddleware,
allow_origins=["https://app.example.com", "https://admin.example.com"],
allow_credentials=True,
allow_methods=["GET", "POST", "PUT", "DELETE"],
allow_headers=["Authorization", "Content-Type"],
)
Missing Rate Limiting
AI-generated APIs almost never include rate limiting. The generated code handles authentication, validation, and business logic but leaves the API open to unlimited request volume.
Insecure Direct Object References
AI generates endpoints using sequential integer IDs, making enumeration trivial:
// AI-GENERATED — ENUMERABLE
router.get('/api/users/:id', async (req, res) => {
const user = await User.findById(req.params.id); // id = 1, 2, 3, ...
res.json(user);
});
// SECURE
router.get('/api/users/:id', async (req, res) => {
if (!isValidUUID(req.params.id)) return res.status(400).json({ error: 'Invalid ID' });
const user = await User.findById(req.params.id); // UUID — not enumerable
if (!user) return res.status(404).json({ error: 'Not found' });
if (user.id !== req.user.id && !req.user.isAdmin) {
return res.status(403).json({ error: 'Forbidden' });
}
res.json(sanitizeUser(user)); // Response schema limits exposed fields
});
SQL Injection in Dynamic Queries
When AI generates complex query logic (filtering, sorting, searching), it frequently falls back to string concatenation:
# AI-GENERATED — SQL INJECTION
@app.get("/api/v1/products")
async def search_products(sort_by: str = "name", order: str = "asc"):
query = f"SELECT * FROM products ORDER BY {sort_by} {order}"
# sort_by = "name; DROP TABLE products; --"
return await database.fetch_all(query)
# SECURE
ALLOWED_SORT_FIELDS = {"name", "price", "created_at"}
ALLOWED_ORDERS = {"asc", "desc"}
@app.get("/api/v1/products")
async def search_products(sort_by: str = "name", order: str = "asc"):
if sort_by not in ALLOWED_SORT_FIELDS:
raise HTTPException(400, "Invalid sort field")
if order.lower() not in ALLOWED_ORDERS:
raise HTTPException(400, "Invalid sort order")
query = f"SELECT * FROM products ORDER BY {sort_by} {order}"
# Now safe because values are restricted to a known-safe set
return await database.fetch_all(query)
12. MCP (Model Context Protocol) Security Vulnerabilities
The Model Context Protocol (MCP) allows AI coding agents to interact with external tools and services. It introduces a new category of security vulnerabilities that are specific to AI-augmented development environments.
Tool Poisoning
MCP tools declare their capabilities through descriptions and schemas. An attacker creates a malicious MCP tool that:
- Declares a benign capability (“format code”) but actually exfiltrates source code
- Provides helpful functionality while silently installing backdoors
- Returns malicious code suggestions that the AI agent incorporates
Mitigation: Only install MCP tools from trusted sources. Audit tool behavior. Monitor network traffic from MCP tool processes.
Remote Code Execution (RCE)
MCP tools that execute code or shell commands can be exploited:
- A malicious tool description includes hidden instructions that cause the AI to pass dangerous parameters
- Tool input schemas do not validate parameters, allowing injection
- Tool execution environments have excessive permissions
Mitigation: Sandbox MCP tool execution. Use minimal permissions. Validate all tool inputs. Never run MCP tools as root or with admin privileges.
Overprivileged Access
MCP tools often request broad permissions to function:
- File system access to the entire repository
- Network access to arbitrary endpoints
- Database access with write permissions
- Shell execution capability
Mitigation: Apply least privilege. Grant only the specific permissions each tool needs. Use read-only access where writes are not required. Restrict network access to known-needed endpoints.
Supply Chain Tampering
MCP tool packages can be compromised like any software dependency:
- Typosquatting of popular MCP tool names
- Compromised maintainer accounts pushing malicious updates
- Dependency confusion attacks on MCP tool dependencies
Mitigation: Pin MCP tool versions. Verify checksums. Use approved tool registries. Monitor for suspicious tool updates.
Real-World Incident: Supabase Cursor Agent
In a documented incident, attackers exploited the MCP protocol in a Cursor development environment:
- Attackers embedded SQL instructions via prompt injection into data that was loaded through an MCP tool connected to a Supabase database
- When the Cursor agent processed this data, it interpreted the embedded SQL instructions as commands
- The agent executed the SQL against the database, performing unauthorized data modifications
- The attack was successful because the MCP tool had write access to the database and the AI agent did not distinguish between data content and executable instructions
Lessons:
- MCP tools with database access should use read-only credentials by default
- Data loaded through MCP tools must be treated as untrusted input
- AI agents must not execute instructions found in data payloads
- Database MCP tools should implement query allowlisting, not arbitrary SQL execution
13. API Security Testing
Contract Testing
Verify that the API implementation matches its specification (OpenAPI, AsyncAPI):
- Every endpoint in the spec is implemented
- Every implemented endpoint is in the spec (no undocumented endpoints)
- Request/response schemas match the spec
- Error responses match documented error schemas
- Authentication requirements match the spec
Tools: Schemathesis, Dredd, Prism (Stoplight)
Fuzz Testing
Send random, malformed, and boundary-case inputs to discover unexpected behavior:
- Invalid JSON (malformed, deeply nested, extremely large)
- Boundary values (maximum integers, empty strings, null, Unicode edge cases)
- Type confusion (string where integer expected, array where object expected)
- Injection payloads (SQL, XSS, command injection, LDAP, XML)
Tools: Schemathesis (schema-aware fuzzing), RESTler (stateful API fuzzing), Burp Suite, OWASP ZAP
Authentication Testing
- Verify all endpoints require authentication (except explicitly public ones)
- Test with expired tokens, revoked tokens, tokens from different environments
- Test token reuse after password change
- Test cross-tenant token usage (token from org A used on org B’s endpoints)
- Verify rate limiting on authentication endpoints
Authorization Testing
- Test BOLA: access objects belonging to other users of the same role
- Test BFLA: access admin endpoints with regular user tokens
- Test privilege escalation: modify your own role/permissions
- Test horizontal privilege escalation: access resources of users at the same privilege level
- Test mass assignment: include admin-only fields in update requests
14. API Documentation Security
OpenAPI Spec Security Schemes
components:
securitySchemes:
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
apiKey:
type: apiKey
in: header
name: X-API-Key
oauth2:
type: oauth2
flows:
authorizationCode:
authorizationUrl: https://auth.example.com/authorize
tokenUrl: https://auth.example.com/token
scopes:
read:invoices: Read invoice data
write:invoices: Create and update invoices
admin: Full administrative access
security:
- bearerAuth: [] # Applied globally
Redacting Sensitive Information
- Do not document internal-only endpoints in external API documentation
- Use separate documentation sets for internal and external consumers
- Do not include example tokens, API keys, or credentials in documentation
- Redact server URLs in public documentation (use
https://api.example.com, not actual internal hostnames) - Do not document rate limit exact thresholds in public docs (prevents attackers from optimizing to just below limits)
15. Summary and Key Takeaways
-
APIs are the primary attack surface: 83% of web traffic, 681% increase in attacks. Dedicated API security is not optional.
-
OWASP API Security Top 10 is your baseline: BOLA (#1) and Broken Authentication (#2) are the most exploited. Every endpoint needs object-level and function-level authorization.
-
Authentication is not authorization: Verifying identity (who you are) is distinct from verifying permission (what you can do). Both must be checked, on every request, server-side.
-
AI-generated API code is consistently missing authorization: The most common AI API vulnerability is functional code that authenticates but does not authorize at the object level.
-
MCP introduces a new attack class: Tool poisoning, RCE, overprivileged access, and supply chain tampering. The Supabase/Cursor incident demonstrates real-world exploitation.
-
GraphQL and gRPC have unique attack vectors: Query depth, introspection, batching (GraphQL) and message size, streaming (gRPC) require specific mitigations beyond REST API security.
-
API security testing must be specific: Contract testing, fuzz testing, authentication testing, and authorization testing — each addresses different vulnerability categories.
Lab Exercise
Exercise 3.5: API Security Assessment
Part A: Vulnerability Identification (45 minutes)
You will receive an OpenAPI specification and the corresponding API source code for a sample application with 10 intentional vulnerabilities spanning the OWASP API Security Top 10. For each vulnerability:
- Identify the OWASP API category
- Explain the attack scenario
- Write the remediated code
- Write an automated test that verifies the vulnerability is fixed
Part B: AI-Generated API Review (30 minutes)
Use an AI coding tool to generate a complete CRUD API for a “document management” system. Then:
- Run SAST tools on the generated code
- Manually identify all BOLA/BFLA vulnerabilities
- Check for CORS, rate limiting, and input validation completeness
- Document the total vulnerability count and remediation effort
Part C: MCP Security Configuration (15 minutes)
Given a list of 5 MCP tools with their requested permissions, create a least-privilege permission configuration that:
- Grants only necessary permissions for each tool
- Restricts file system access to specific directories
- Limits network access to required endpoints only
- Separates read and write permissions
- Documents the security rationale for each permission decision
Part D: API Security Testing (30 minutes)
Using Schemathesis or a similar tool:
- Run schema-aware fuzzing against a sample API
- Identify all findings
- Triage findings into true positives and false positives
- For true positives, implement fixes and verify they resolve the finding
Deliverable: Vulnerability analysis, AI code review report, MCP configuration, fuzzing results Time: 2 hours total
Module 3.5 Complete. Track 3 (Secure Development — Developers) Complete.
Study Guide
Key Takeaways
- 83% of web traffic is API traffic — With 681% increase in API attacks, dedicated API security is essential beyond traditional web application security.
- BOLA is #1 because it is easy to exploit, hard to detect — Changing one parameter (object ID) accesses another user’s data; authorization must be checked at the object level on every request.
- Authentication is not authorization — Verifying identity (who you are) is distinct from verifying permission (what you can do); both checked on every request, server-side.
- AI-generated API code consistently lacks authorization — Most common AI API vulnerability: functional endpoints that authenticate but do not authorize at the object or function level.
- MCP introduces a new attack class — Tool poisoning, RCE, overprivileged access, and supply chain tampering; the Supabase/Cursor incident demonstrates real-world exploitation via prompt injection through MCP.
- GraphQL has unique attack vectors — Query depth abuse, introspection exposure, batching attacks, and field-level authorization gaps require specific mitigations.
- Rate limiting is multi-layered — Per-client, per-endpoint, per-operation cost; use 429 with Retry-After header; enforce at API gateway for consistency.
Important Definitions
| Term | Definition |
|---|---|
| BOLA | Broken Object Level Authorization — API #1: accessing objects belonging to other users by changing an identifier |
| BFLA | Broken Function Level Authorization — API #5: invoking operations the user is not authorized to perform |
| PKCE | Proof Key for Code Exchange — prevents authorization code interception in OAuth 2.0 flows |
| mTLS | Mutual TLS — both client and server present certificates for strong mutual authentication |
| SSRF | Server-Side Request Forgery — API fetches user-supplied URLs, allowing access to internal services |
| MCP | Model Context Protocol — allows AI agents to interact with external tools; introduces tool poisoning and RCE risks |
| GraphQL Introspection | Queries revealing the entire API schema; must be disabled or restricted in production |
| API Gateway | Centralized enforcement point for authentication, rate limiting, and security headers |
Quick Reference
- Framework/Process: OWASP API Security Top 10 (2023); OAuth 2.0 with PKCE; JWT claim validation (iss, aud, exp, nbf, sub, iat); RBAC/ABAC/scope-based authz
- Key Numbers: 83% API traffic; 681% attack increase; 15,000+ average enterprise endpoints; 429 for rate limiting; 7-10 max GraphQL query depth; JWT signing key rotation every 90 days
- Common Pitfalls: CORS set to wildcard (*) on authenticated endpoints; missing rate limiting entirely; using sequential integer IDs enabling enumeration; trusting third-party API responses without validation; leaving GraphQL introspection enabled in production
Review Questions
- What is the key difference between BOLA (API1) and BFLA (API5), and why does each require different mitigations?
- Why should JWT signing use asymmetric algorithms (RS256/EdDSA) in multi-service environments rather than HS256?
- How did the Supabase/Cursor MCP attack work, and what controls would have prevented it?
- What GraphQL-specific security controls are needed beyond standard REST API security?
- Why is the Implicit OAuth flow deprecated, and what should replace it?