API Security
Securing APIs against the OWASP API Top 10: broken object-level authorisation, excessive data exposure, mass assignment, API key management, rate limiting, and GraphQL-specific mitigations.
What it is
API security addresses the unique vulnerabilities introduced by machine-to-machine communication interfaces. Unlike traditional web security, which focuses on HTML rendering and user sessions, API security must protect programmatic access patterns where inputs are structured (JSON, XML, URL parameters), authentication is token-based, and endpoints are called by automated clients. The OWASP API Security Top 10 catalogues the most critical API-specific vulnerabilities, distinct from the OWASP Web Application Top 10. The leading API vulnerability is Broken Object-Level Authorisation (BOLA / IDOR): an API that accepts a resource identifier (e.g., /api/orders/12345) but fails to verify that the authenticated user owns resource 12345 allows horizontal privilege escalation — any authenticated user can access any other user's data by iterating IDs.
Other critical OWASP API risks include: Broken Authentication (weak API key schemes, missing token expiry), Excessive Data Exposure (returning full data models and relying on the client to filter — a mobile app might show 5 fields but the API returns 50, including sensitive ones), Mass Assignment (blindly binding request body fields to data model objects, allowing a user to set fields like isAdmin: true), Security Misconfiguration (debug endpoints left enabled, verbose error messages revealing stack traces), and Injection (SQL, NoSQL, command injection via API parameters). GraphQL introduces additional surface: introspection exposes the full schema to attackers, deeply nested queries can cause denial of service by triggering deeply recursive database joins, and field-level authorisation must be implemented explicitly as GraphQL does not have built-in field visibility controls.
Why it exists
APIs have become the primary attack target because they are the authoritative data access layer for all application types (web, mobile, IoT, third-party integrations). The Verizon Data Breach Investigations Report has consistently identified web application attacks (primarily API exploitation) as the top action in data breaches. API-specific vulnerabilities are often invisible to traditional web application scanners, which check for HTML-form-based attacks but miss BOLA, mass assignment, and GraphQL depth attacks. The programmatic, predictable nature of API endpoints makes them highly amenable to automated scanning and exploitation: an attacker can enumerate /api/orders/1 through /api/orders/1000000 in minutes to exfiltrate all orders in the database if BOLA is not mitigated.
When to use
- All public-facing APIs — OWASP API security controls are baseline requirements.
- Internal microservice APIs — BOLA and mass assignment vulnerabilities are exploitable by internal attackers or via lateral movement.
- GraphQL APIs — require additional controls beyond REST (query depth limiting, complexity analysis, introspection disabling in production).
- Partner or third-party API integrations — API key management and scoped authorisation are essential.
When not to use
- These are not "use or not use" controls — all APIs require security. The question is which specific controls apply. For read-only public APIs with no user data, BOLA is less relevant but rate limiting and injection prevention still apply.
Typical architecture
API SECURITY LAYERS:
Client → API Gateway → Backend Service
↑
[Auth: JWT validation (iss, aud, exp, sig)]
[Rate limiting: per API key, per user, per IP]
[Input validation: schema validation pre-routing]
[Logging: all requests + response codes]
BOLA MITIGATION:
❌ VULNERABLE:
GET /api/orders/{orderId}
// Fetches order by ID, no ownership check
order = db.getOrder(orderId)
return order
✅ SECURE:
GET /api/orders/{orderId}
// Always scope query to authenticated user
order = db.getOrder(orderId, userId=currentUser.id)
if (!order) return 403 // not found or not owned
MASS ASSIGNMENT MITIGATION:
❌ VULNERABLE:
user.update(request.body) // blindly copies all fields
✅ SECURE:
allowedFields = ['name', 'email', 'bio']
updates = pick(request.body, allowedFields)
user.update(updates)
GRAPHQL SECURITY:
- Disable introspection in production
- Query depth limiting: max depth = 7
- Query complexity analysis: abort if score > 1000
- Field-level auth middleware on resolver
- Rate limit: queries per minute per token
API KEY MANAGEMENT:
- Keys stored hashed (sha256) in DB
- Keys prefixed with service name: "sk_live_xxx"
- Scoped: read-only vs read-write keys
- TTL: keys expire and must be rotated
- Last-used timestamp tracked for audit
Pros and cons
Pros
- Object-level ownership checks in all data retrieval and modification endpoints prevent the most common API breach vector.
- Allowlist-based field binding (rather than mass assignment) makes it impossible to set unexpected fields via API request bodies.
- API gateway–level rate limiting provides automated protection against credential stuffing, enumeration, and scraping attacks.
- Explicit response schemas prevent accidental sensitive data exposure — only approved fields are included in API responses.
Cons
- Object-level authorisation checks must be implemented consistently in every endpoint; a single missed check creates a vulnerability.
- Rate limiting requires careful tuning — too strict blocks legitimate high-volume API consumers, too loose provides no protection.
- GraphQL complexity analysis requires upfront effort to assign complexity scores to each field and calibrate acceptable thresholds.
- Disabling GraphQL introspection in production means developers cannot use GraphiQL, requiring alternative documentation solutions.
Implementation notes
Implement BOLA protection as a mandatory architectural pattern: every database query that retrieves a resource by ID must include the authenticated user's ID (or tenant ID) as an additional filter condition. This pattern should be enforced through code review and automated testing with authorisation tests that verify cross-user access is denied. The authorisation logic belongs in the service layer, not the API gateway — the gateway validates the token but cannot know the ownership semantics of individual resources.
For excessive data exposure, define explicit response DTOs (Data Transfer Objects) rather than returning ORM model objects directly. Use serialisation libraries that require explicit field inclusion (opt-in) rather than opt-out exclusion. For pagination, enforce maximum page size limits to prevent bulk data extraction. For error handling, return generic error messages to API clients (do not include stack traces, SQL error messages, or internal service names), but log full details server-side. Use structured error responses with error codes that developers can reference in documentation.
API keys should be treated as credentials, not configuration values: hash them before storage, display them only once at generation time, prefix with a service/environment indicator (sk_live_, sk_test_), scope them to minimum necessary permissions, and set expiry dates for automated rotation. For high-value APIs, use mutual TLS (mTLS) between partners rather than API keys — certificates provide stronger identity guarantees and are harder to phish.
Common failure modes
- BOLA via integer ID enumeration: Sequential integer IDs in REST URLs (
/api/users/1001) are trivial to enumerate. Use UUIDs or opaque IDs — but this reduces friction, it does not replace ownership checks, which are still mandatory. - Verbose error messages: APIs returning
"User email@example.com not found"confirm to an attacker whether an email address is registered — use"Invalid credentials"uniformly. - Missing rate limiting on auth endpoints: Login, password reset, and OTP verification endpoints without rate limits are vulnerable to credential stuffing and brute force attacks.
- Admin API endpoints accessible to regular users: Admin operations (
GET /api/admin/users) that rely only on "obscurity" (the endpoint isn't in docs) rather than proper role checks are exploitable by anyone who discovers the endpoint path. - GraphQL introspection enabled in production: Introspection gives attackers a complete map of all types, fields, and mutations, dramatically accelerating exploitation.
Decision checklist
- Are all data retrieval endpoints scoped to the authenticated user/tenant (BOLA prevention)?
- Is field binding explicit (allowlist) rather than implicit (mass assignment)?
- Are API responses defined as explicit DTOs with no extra fields?
- Is rate limiting applied per-user and per-IP at the API gateway?
- Are GraphQL introspection, query depth, and complexity limits configured in production?
- Are API keys hashed in storage with defined scopes and expiry?
Example use cases
- Financial services API: Banking API enforces object-level authorisation on all account and transaction endpoints; account access requires both valid JWT and verified account ownership; all access logged for compliance audit.
- GraphQL BFF: Backend-for-frontend GraphQL API disables introspection in production, applies per-query complexity scoring (mutations capped at 200, queries at 1000), and implements per-field authorisation middleware that checks user roles before resolving sensitive fields.
- Partner API gateway: Partner API keys scoped to specific resources (
orders:read,inventory:write), rate limited to 10,000 requests/hour, keys hashed in database and stored only in partner's secrets manager.
Related patterns
- API Authentication — Token-based authentication for API security.
- API Rate Limiting — Rate limiting implementation strategies.
- Network Security Architecture — WAF as a complementary API protection layer.