3.5 — API Security

Secure Implementation 360 min Developers
0:00 / 0:00
Listen instead
API Security
0:00 / 0:00

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

CharacteristicTraditional Web AppAPI
ClientBrowser (known, constrained)Any HTTP client (unknown, unconstrained)
AuthenticationSession cookiesTokens (JWT, API keys, OAuth)
InputHTML forms (structured)Arbitrary JSON/XML/binary payloads
OutputHTML pagesStructured data (JSON, Protobuf, GraphQL)
StateServer-side sessionsOften stateless (token-based)
ConsumersHumansMachines, mobile apps, other services
Rate of changePages change infrequentlyEndpoints change with every sprint
DiscoveryCrawlableRequires 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.

OWASP API Security Top 10 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 (exp claim)
  • 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-Key or Authorization), 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_uri exactly (no open redirector via partial matching)
  • Implement token revocation for compromised tokens
  • Use the state parameter to prevent CSRF
  • Never use Implicit flow (deprecated due to token exposure in URL fragment)

JWT (JSON Web Tokens)

Claims to validate:

ClaimValidationRisk if Missing
iss (issuer)Must match expected issuerToken from wrong authority accepted
aud (audience)Must match this service’s identifierToken intended for another service accepted
exp (expiration)Must be in the futureExpired tokens accepted
nbf (not before)Must be in the pastTokens used before intended time
sub (subject)Must be a valid user/serviceAuthorization applied to wrong entity
iat (issued at)Must be reasonableReplay 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-After header 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

StrategyExampleSecurity Consideration
URL path/api/v1/usersOld versions remain accessible; must be patched or removed
Query parameter/api/users?version=1Easy to manipulate; less visible in access logs
HeaderAccept: application/vnd.api.v1+jsonNot visible in URL-based monitoring tools
Content negotiationAccept: application/json; version=1Complex; 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:

  1. Announce deprecation with timeline (6 months minimum for production APIs)
  2. Monitor deprecated version traffic to identify remaining consumers
  3. Return Deprecation header on all responses: Deprecation: true
  4. Implement sunset date enforcement: after the deadline, return 410 Gone
  5. 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-validate for declarative validation rules in .proto files
  • 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:

  1. MCP tools with database access should use read-only credentials by default
  2. Data loaded through MCP tools must be treated as untrusted input
  3. AI agents must not execute instructions found in data payloads
  4. 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

  1. APIs are the primary attack surface: 83% of web traffic, 681% increase in attacks. Dedicated API security is not optional.

  2. 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.

  3. 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.

  4. 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.

  5. MCP introduces a new attack class: Tool poisoning, RCE, overprivileged access, and supply chain tampering. The Supabase/Cursor incident demonstrates real-world exploitation.

  6. GraphQL and gRPC have unique attack vectors: Query depth, introspection, batching (GraphQL) and message size, streaming (gRPC) require specific mitigations beyond REST API security.

  7. 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:

  1. Identify the OWASP API category
  2. Explain the attack scenario
  3. Write the remediated code
  4. 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:

  1. Run SAST tools on the generated code
  2. Manually identify all BOLA/BFLA vulnerabilities
  3. Check for CORS, rate limiting, and input validation completeness
  4. 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:

  1. Grants only necessary permissions for each tool
  2. Restricts file system access to specific directories
  3. Limits network access to required endpoints only
  4. Separates read and write permissions
  5. Documents the security rationale for each permission decision

Part D: API Security Testing (30 minutes)

Using Schemathesis or a similar tool:

  1. Run schema-aware fuzzing against a sample API
  2. Identify all findings
  3. Triage findings into true positives and false positives
  4. 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

  1. 83% of web traffic is API traffic — With 681% increase in API attacks, dedicated API security is essential beyond traditional web application security.
  2. 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.
  3. 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.
  4. 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.
  5. 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.
  6. GraphQL has unique attack vectors — Query depth abuse, introspection exposure, batching attacks, and field-level authorization gaps require specific mitigations.
  7. 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

TermDefinition
BOLABroken Object Level Authorization — API #1: accessing objects belonging to other users by changing an identifier
BFLABroken Function Level Authorization — API #5: invoking operations the user is not authorized to perform
PKCEProof Key for Code Exchange — prevents authorization code interception in OAuth 2.0 flows
mTLSMutual TLS — both client and server present certificates for strong mutual authentication
SSRFServer-Side Request Forgery — API fetches user-supplied URLs, allowing access to internal services
MCPModel Context Protocol — allows AI agents to interact with external tools; introduces tool poisoning and RCE risks
GraphQL IntrospectionQueries revealing the entire API schema; must be disabled or restricted in production
API GatewayCentralized 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

  1. What is the key difference between BOLA (API1) and BFLA (API5), and why does each require different mitigations?
  2. Why should JWT signing use asymmetric algorithms (RS256/EdDSA) in multi-service environments rather than HS256?
  3. How did the Supabase/Cursor MCP attack work, and what controls would have prevented it?
  4. What GraphQL-specific security controls are needed beyond standard REST API security?
  5. Why is the Implicit OAuth flow deprecated, and what should replace it?
API Security
Page 1 of 0 ↧ Download
Loading PDF...

Q1. What percentage of web traffic is now API traffic according to Cloudflare 2025 data?

Q2. Which vulnerability is ranked #1 in the OWASP API Security Top 10 (2023)?

Q3. What is the key difference between BOLA (API1) and BFLA (API5)?

Q4. Why should the Implicit flow never be used in OAuth 2.0?

Q5. What is the most common vulnerability in AI-generated API code according to the module?

Q6. In the Supabase/Cursor MCP attack incident, how did attackers exploit the system?

Q7. What is the recommended maximum query depth limit for GraphQL APIs?

Q8. Why should JWT signing use asymmetric algorithms (RS256, EdDSA) in multi-service environments rather than HS256?

Q9. What response code should be returned when a client exceeds API rate limits?

Q10. Why should GraphQL introspection be disabled or restricted in production?

Answered: 0 of 10 · Score: 0/0 (0%)