The 30-second version: APIs are the dominant attack surface of any modern cloud-native business - the OWASP API Top 10 exists because the bugs are different from web-app bugs. WAFs, CSP headers, and code-scanning miss most of them because the worst API issues (BOLA, BFLA, BOPLA, unrestricted resource consumption) are business-logic failures that look like legitimate traffic.
The pragmatic stack: an API gateway that enforces auth, rate limiting, and schema validation; OAuth 2.0 / OIDC for delegated auth, with JWT validation done carefully; fine-grained authorization per object (BOLA-by-design); schema-first APIs validated at the edge; discovery tooling so shadow APIs surface; and a runtime API security platform that watches the traffic for the business-logic abuses signatures can't catch.
On this page
- Why API security is its own discipline now
- The OWASP API Security Top 10 (2023)
- Authentication patterns
- Authorization patterns
- The BOLA problem
- Rate limiting
- API gateway landscape
- Schema validation
- GraphQL-specific risks
- gRPC-specific concerns
- SSRF prevention in APIs
- Discovery & inventory
- Runtime API security platforms
- API testing
- Webhooks security
- Third-party API consumption
- APIs in the service mesh
- AWS, Azure, and GCP side-by-side
- Maturity stages
- Common pitfalls
- Further reading
- FAQ
- Where next
Why API security is its own discipline now
The web in 2010 was rendered HTML; the web in 2026 is the JSON behind the rendered HTML, plus mobile clients, plus B2B integrations, plus webhook callbacks, plus the service-to-service traffic inside your own cluster. The API is the product surface, and it's the attack surface.
The shift created bugs the application-security toolchain didn't anticipate. A ModSecurity rule set is unhelpful against an authenticated user asking for someone else's order. A Content-Security-Policy header doesn't apply to a mobile client or a partner pulling from a webhook. SAST tooling that grep'd for SQL injection patterns doesn't catch that GET /api/orders/{id} never re-checks ownership.
The category is now formal enough to have its own OWASP top-10 (2019 first edition, 2023 the current), its own analyst category (Gartner's "API security" was carved out of WAF in 2021), and a dedicated vendor landscape (Salt, Traceable, Noname/Akamai, Cequence, Wallarm, 42Crunch). If you operate non-trivial APIs and you're treating API security as "the WAF will catch it," you are operating on a 2015 model of the web.
What changed
- Authorization is per-object, not per-endpoint. "Can this user call /orders?" is the easy question; "can this user see this order?" is the question modern auth middleware doesn't answer by default.
- Authentication moved out of the cookie jar. Bearer tokens, JWTs, mTLS - each with its own pitfalls and none with browser-supplied defaults.
- Traffic shapes are diverse. A REST endpoint, a GraphQL query, a gRPC stream, and a webhook delivery look nothing alike to a WAF.
- The inventory problem is real. Most non-trivial orgs cannot enumerate every API they expose, what data it returns, who owns it, and whether it's still in use.
- Business-logic abuse looks legitimate. The attacker is logged in. Their requests pass the schema. The bug is in what the endpoint does with the request - and only behavioral analytics catches it.
The OWASP API Security Top 10 (2023)
The OWASP API Top 10 is the de-facto checklist. The 2023 edition reflects a maturity shift from 2019 - BOLA still dominates, but the list now reflects how often authorization, not authentication, is the failure mode. Walking through each:
API1:2023 - Broken Object Level Authorization (BOLA)
The single most common API bug. The endpoint authenticates the caller and confirms they can call this kind of endpoint, but does not check whether the specific object referenced in the request belongs to that caller. GET /api/orders/1247 returns order 1247 regardless of who owns it. See the BOLA section for the architectural defense.
API2:2023 - Broken Authentication
Tokens that can be forged, replayed, brute-forced, or stolen via misconfigured flows. The dominant cases: JWTs accepted without signature verification, OAuth flows that don't validate the audience, password-reset flows that leak tokens, and authentication endpoints without rate limiting. Treat the auth surface as critical; subject it to dedicated review and rate-limit it harder than anything else.
API3:2023 - Broken Object Property Level Authorization (BOPLA)
The endpoint correctly checks that the caller owns the object, but does not check which properties they can read or write. Mass-assignment (the caller PATCHes {is_admin: true} and the server accepts it) is the write side; over-fetching (the caller GETs the object and the response includes fields they shouldn't see) is the read side. Defended by allowlisting properties in DTOs, never spreading user input into the model, and never returning the model directly.
API4:2023 - Unrestricted Resource Consumption
The endpoint accepts a request that costs the server far more than the caller. Examples: pagination with no max page-size, file uploads with no size limit, search queries with no time budget, deeply nested GraphQL queries, batch endpoints with no batch-size cap. Defended by per-endpoint quotas, per-user budgets, query-complexity limits, and circuit breakers on downstream calls. The relevant cloud-cost angle: an attacker exploiting this against your serverless API may not breach data but will breach your AWS bill.
API5:2023 - Broken Function Level Authorization (BFLA)
The same shape as BOLA but for functions rather than objects: the caller can invoke an admin-only function because the function-level check is missing. POST /api/admin/users/123/delete succeeds for a non-admin caller because the admin role-check is in the UI, not the API. Defended by central authorization middleware that enumerates required permissions per route, with deny-by-default.
API6:2023 - Unrestricted Access to Sensitive Business Flows
The endpoint is technically working as designed but is being abused at scale - bulk account creation, ticket scalping, gift-card balance enumeration, scraping product catalogs. The auth and authorization pass; the abuse is in the volume. Defended at the platform layer: behavioral analytics (the API security platforms are heavily focused here), bot management, fraud-detection signals, and stepped-up auth for high-value flows.
API7:2023 - Server Side Request Forgery (SSRF)
The API accepts a URL from the client and fetches it on the server - image-import, webhook-test, URL-preview, OAuth-callback features all do this. An attacker supplies a URL pointing at the instance metadata service, an internal admin endpoint, or a cloud-internal API. See SSRF prevention for the defense pattern.
API8:2023 - Security Misconfiguration
The grab-bag: verbose errors revealing stack traces, CORS misconfigurations that allow any origin, missing security headers, default credentials on management endpoints, debug routes left enabled, GraphQL introspection in production, gRPC reflection in production. Defended by baselined gateway configs, a security-config policy that lives in the IaC, and CSPM-style scanning of API gateways and APIM resources.
API9:2023 - Improper Inventory Management
Old API versions still deployed (v1 of the API was supposed to be off; it isn't), staging environments accessible from the internet, deprecated endpoints with weaker auth still online, and the broader "shadow API" problem of endpoints nobody knows exist. The inventory problem is now a first-class top-10 item - see discovery & inventory.
API10:2023 - Unsafe Consumption of APIs
The flip side - your code calls a third-party API and trusts the response without validation, exposing you to vulnerabilities the upstream introduces. Compromised upstream provider returns malicious payloads, or unsafe deserialization on response data. Defended by validating responses the same way you validate requests, timeouts and circuit breakers on outgoing calls, and treating third-party API responses as untrusted input.
Authentication patterns
The credential the client presents and how you validate it. The five patterns you'll encounter:
API keys
An opaque string the caller includes in a header. Fine for low-risk, low-value, server-to-server contexts where rotation is procedural and the blast radius of a leak is small (read-only public datasets, sandbox environments, "click here for a free key" demo APIs). Not fine for anything that touches user data - there's no user identity, no expiration by default, and no way to scope an individual call. If you use API keys, at minimum: rotate them on a schedule, scope them per-environment, log every use, and rate-limit them aggressively.
OAuth 2.0 / OIDC
The dominant pattern for any non-trivial API in 2026. OAuth 2.0 is the authorization framework - how a client gets a token to call your API on a user's behalf. OIDC is the identity layer on top - how the client learns who the user actually is. Use OIDC for user authentication, OAuth 2.0 for delegated access. Implement OAuth 2.1 conventions: PKCE everywhere, no implicit flow, no password grant, refresh-token rotation. The grant flows you'll actually use: authorization code + PKCE (web and mobile), client credentials (service-to-service), device code (TVs, CLIs).
JWT (and the pitfalls)
A JWT - JSON Web Token - is a signed (and optionally encrypted) blob of claims that the bearer can present to APIs. It's a transport, not an auth system. The pitfalls are notorious enough that JWT validation has its own published guide (RFC 8725 - JWT Best Current Practices). The recurring failure modes:
alg: none- the JWT header says "no signature, just trust me." Old libraries accepted this. Modern libraries reject it, but be explicit in your code: pin allowed algorithms.- Algorithm confusion - token signed with HS256 (symmetric) is verified with RS256 (asymmetric) using the public key as the HMAC secret, succeeds. Pin the verification algorithm to the one your IdP issues.
kidpath injection - the key-id header is a filesystem path or URL, an attacker controls it, your code reads from there. Treat kid as an opaque identifier you look up in a fixed table.- Weak HMAC secrets - short, guessable HS256 secrets get cracked. Use asymmetric (RS256, ES256) keys; if you must use HMAC, use a high-entropy secret.
- No expiration check, no audience check, no issuer check - the most common bug. Every JWT validation must verify
exp,iat,iss,aud, and signature. - JWKS endpoint not pinned - your service trusts whichever JWKS the IdP serves. Pin the issuer URL; cache and rotate the JWKS deliberately; reject keys not in your trust set.
- Logout doesn't revoke - JWTs are stateless; "logout" doesn't invalidate the token. Use short expirations, refresh tokens with revocation, or a denylist for high-value sessions.
mTLS
Mutual TLS - both ends present certificates. The strongest authentication on the network: a certificate can't be replayed off the wire by an HTTP intermediary, and the lifecycle is enforced by the CA. The operational cost is real (cert issuance, rotation, revocation, partner onboarding), so mTLS shows up most where the data is sensitive enough to justify it: service-to-service inside a service mesh, high-value partner integrations, and any compliance regime that mandates client authentication.
Session cookies (for browser-fronted APIs)
If your API is consumed only by your own browser SPA on the same origin, a session cookie set after a login still works - Secure, HttpOnly, SameSite=Strict (or Lax, depending on cross-tab needs), and a server-side session store. The advantage is browser-supplied CSRF/XSS defenses; the cost is no mobile or partner support. Most modern stacks use OAuth even for the SPA so the same backend supports browsers, mobile, and partners with one auth model.
Authorization patterns
Authentication says "this caller is X." Authorization says "X is allowed to do Y on resource Z." The dominant models:
RBAC - Role-Based Access Control
Permissions group into roles; users have roles. The simplest mental model, the easiest to audit, and what most internal admin tools and many B2B SaaS apps run on. Limitations show up when permission decisions depend on the resource itself (ownership, project, organization, classification) - at which point you bolt on ABAC or move up the stack.
ABAC - Attribute-Based Access Control
Decisions evaluated against attributes - user attributes, resource attributes, environment attributes, action attributes. More expressive than RBAC for the "this user can read this object because they own it AND we're in business hours AND the object isn't classified" case. Harder to reason about; usually expressed in a policy language.
The policy-as-code stack: OPA, Cedar, AuthZed/SpiceDB, Topaz
- Open Policy Agent (OPA) - CNCF graduated, vendor-neutral, runs anywhere. Rego is the policy language. Used for API authorization, Kubernetes admission, Terraform pre-apply, and as a general policy decision point. The most common "we want fine-grained authorization, don't want to invent a language" answer.
- Cedar - AWS-authored, open-source policy language. Pulls from the IAM heritage; strong analyzability story (can prove what a policy will and won't allow). Underpins Amazon Verified Permissions.
- AuthZed / SpiceDB - relationship-based authorization (ReBAC), modeled after Google's Zanzibar paper. Best fit for the "who can read this document because they're in the team that owns the folder in the workspace" kind of permission graph.
- Topaz - open-source authorization built on OPA and a directory model.
- Vendor: Permit.io, Styra, Oso, Aserto - commercial overlays that bring management UI, audit logs, and policy lifecycle on top of these engines.
Scopes vs roles
OAuth scopes are coarse permissions attached to a token at issuance ("this token can read calendars, not write them"). Roles are user-attached permissions resolved at request time. They serve different layers: scopes are what the user delegates to the client; roles are what the user is allowed to do, period. A common confusion: trying to express fine-grained per-object permissions as scopes (token explodes), or expressing client capability as roles (the user logically can do it, but you wanted to limit this app). The pragmatic split: scopes for client capability, roles/policies for user permissions, evaluated together at the API.
The BOLA problem
BOLA - Broken Object Level Authorization - is API1 on the OWASP top-10 for a reason. Almost every API has it somewhere, at least once in the codebase's history, because the bug fits naturally into how developers write controllers.
Why it's nearly universal
A typical handler:
@app.get("/api/orders/{order_id}")
def get_order(order_id, user=current_user):
order = db.orders.find(order_id)
return order
The framework's auth middleware confirmed current_user is logged in and can call this route. The handler looks up the order and returns it. The missing check: does this user own this order?
The check is easy to write, but it has to be written into every handler that takes an object id - and one missed handler is a complete data-disclosure bug. Multiply by every list-style endpoint that paginates over objects the caller may not own, every nested resource (/api/orgs/{org_id}/users/{user_id}/orders/{order_id} - three checks), every PATCH that targets an object id in the body, every search that returns ids the caller can then fetch directly.
How to design it out
- Scope queries through an authorization layer. Every database lookup goes through a method that takes
user_idas well asobject_id.db.orders.find(order_id, owner_id=user.id)returns the order only if the user owns it; the handler never has a path to bypass. - Use opaque, unpredictable identifiers. UUIDs or hashids reduce the cost of a missed check - an attacker can't enumerate
/api/orders/1, /api/orders/2. This is defense in depth, not the fix; the architectural fix is the ownership check. - Centralize authorization in middleware. An OPA/Cedar/AuthZed-style policy decision point reviews every request: caller, action, resource, attributes. Handlers don't write authz themselves; they call the PDP.
- Test the negative case. Integration tests that create two users, have user A request user B's objects, and assert 404 (not 403 - don't confirm existence) for every object endpoint. Generate the test from the route inventory.
- Detect at runtime. Cross-user object access in the logs is the signal. API security platforms (see below) build user-to-object models and alert when a user accesses objects outside their normal pattern.
The mature answer is to make BOLA architecturally impossible - the database query itself enforces ownership - rather than relying on every developer to remember the check on every handler.
Rate limiting
The crudest and most-effective control. Auth answers "who is calling?"; rate limiting answers "are they calling too much?" - and an attacker who can't make a million requests can't enumerate a million ids, brute-force a million passwords, or run a million expensive queries.
What to limit on
- Per-IP - last resort, dodged by anyone with a botnet or residential-proxy network. Fine for anonymous public endpoints; not the primary defense.
- Per-user - the right granularity for authenticated APIs. Tied to the user-id or token, not the IP. Catches the "logged-in attacker enumerating objects" case.
- Per-API-key / per-client - for service-to-service and partner traffic.
- Per-endpoint - high-cost or sensitive endpoints (password reset, login, search, export) get tighter limits than read endpoints.
- Per-tenant - for B2B SaaS, the tenant is often the right limit unit, with sub-limits per user inside the tenant.
Algorithms
Three you'll see in practice. Token bucket - refills at a rate, each request consumes a token; allows controlled bursts. Sliding window - count requests in the last N seconds. Fixed window with leak - count per discrete time bucket, with smoothing. Token bucket is the default for most gateways; sliding window is preferred where exact rate enforcement matters (anti-abuse, payment).
Fair-use vs anti-abuse
Two distinct concerns that get conflated. Fair-use limits prevent any one customer from monopolizing capacity - generous, mostly never hit, primarily defensive against bugs in client code. Anti-abuse limits target adversarial patterns - login brute-force, scraping, enumeration - and should be tight, surfaced as 429s, and tied to step-up auth or temporary blocking. Run both layers.
Where it's enforced
- Edge / CDN - Cloudflare, AWS WAF rate-based rules, Azure Front Door. Cheapest place to drop floods.
- Gateway - Kong, Envoy, Apigee, AWS API Gateway, Azure APIM, Tyk. Per-route, per-consumer, per-user limits with policy expressions.
- App layer - for limits that need business context (per-org tier, per-feature flag). Redis-backed token buckets are the standard.
API gateway landscape
The API gateway is the policy enforcement point for north-south traffic - auth, rate limiting, schema validation, routing, observability. The pragmatic landscape:
- Kong - open-source plus enterprise, plugin-rich, Lua-extensible, common in self-managed Kubernetes shops. Konnect is the SaaS control plane.
- AWS API Gateway - REST API and HTTP API offerings. Native Lambda integration, Cognito-friendly auth, WAF-friendly. The "you're already on AWS" default.
- Azure API Management (APIM) - strong on developer portal, policy expressions in XML+C#, named values for secrets. Pairs with Entra ID, Defender for APIs.
- GCP Apigee - enterprise-grade, came in via Google's 2016 acquisition; strong analytics and monetization. GCP API Gateway is the lightweight alternative for fronting Cloud Run / Cloud Functions.
- Cloudflare API Gateway - gateway-plus-WAAP, runs at the Cloudflare edge, deep integration with the rest of the Cloudflare stack.
- Tyk - open-source plus enterprise; lightweight, Go-based, multi-cloud.
- KrakenD - high-performance, stateless, configuration-as-code aggregation gateway.
- Solo Gloo Gateway - Envoy-based, Kubernetes-native, gateway and mesh story under one vendor.
- MuleSoft Anypoint - enterprise iPaaS + API management; common in large enterprise integration estates.
- IBM API Connect - enterprise; common in regulated industries with IBM-centric stacks.
The choice usually breaks on three axes: how much you self-manage (Kong open-source vs Apigee managed vs Cloudflare edge), how cloud-native the integration is (AWS API Gateway is friction-free on AWS, friction elsewhere), and how strong the developer-portal and monetization story needs to be (Apigee and APIM lead here). For most cloud-native teams in 2026, the cloud-provider's gateway plus an API security platform layered on top is the right starting place.
Schema validation
The under-used control. If you declare what your API accepts - in OpenAPI 3.x, JSON Schema, or a protobuf definition - and enforce the declaration at the gateway, you eliminate an entire class of bugs: invalid types, oversized payloads, unknown fields (which is the mass-assignment defense), nested objects beyond a depth, arrays beyond a size.
The principle
"If it isn't in the schema, reject it" - the gateway returns 400 before the request ever reaches your handler. The handler can then trust that order_id is a UUID, quantity is a non-negative integer below the cap, and customer is an object with exactly the four allowed fields. Half of the input-validation bugs that show up in app code disappear at the gateway layer.
The mechanics
- OpenAPI 3.1 for REST - JSON Schema 2020-12 compatible, the practical standard. Generated from code (FastAPI, NestJS, Spring) or written first (spec-first design).
- JSON Schema directly for non-OpenAPI payload validation - webhook payloads, internal RPC schemas, queue messages.
- protobuf for gRPC - the type system is the schema.
- GraphQL SDL for GraphQL - the schema is mandatory and queries that don't match are rejected, but you still need to enforce field-level authorization beyond the schema's type checks.
Spec-first vs code-first
Spec-first writes the OpenAPI doc first and generates code stubs from it; code-first writes the handlers and emits the OpenAPI doc from annotations. Spec-first produces cleaner, reviewable contracts and forces design before implementation. Code-first ships faster and stays in sync with the code automatically. The pragmatic answer: spec-first for partner-facing and v1 design; code-first with strict-mode validation for internal and iterative work. Either way: validate at the gateway, not just in tests.
Tooling
- Spectral - OpenAPI linter; catches missing auth, undocumented response codes, schema drift.
- 42Crunch - OpenAPI security audit and gateway-enforced contract validation.
- Swagger Codegen, OpenAPI Generator - client and server stubs.
- Gateway-native validation - AWS API Gateway request validators, Azure APIM
validate-contentpolicy, Kong OAS Validation plugin.
GraphQL-specific risks
GraphQL collapses many REST endpoints into one - and concentrates many REST-style bugs into different shapes.
Introspection in production
Introspection is the GraphQL feature that lets a client query the schema itself - every type, every field, every argument, every enum value, every deprecation. In development it's invaluable. In production, internet-facing, it's free recon for any attacker: they get a complete map of your API in seconds. Disable introspection in production or scope it to authenticated internal callers. Persisted queries (below) make introspection unnecessary for client builds anyway.
Query depth and complexity attacks
A GraphQL query can be arbitrarily deep - { user { friends { friends { friends { ... } } } } } - and a small query string can fan out to enormous server work. Defenses: maximum query depth (typically 7-10), maximum query complexity score (each field weighted; query must score below a cap), and request timeouts. Use a library - graphql-depth-limit, graphql-query-complexity, or the framework's built-in equivalent.
Batching attacks
GraphQL supports batching - multiple queries in one request. An attacker can pack many enumeration queries (e.g., login attempts with different passwords) into a single request and bypass per-request rate limiting. Defenses: limit batch size, count each operation against rate limits separately, or disable batching.
Persisted queries vs ad-hoc
Persisted queries - the client registers a query template by hash with the server, and at runtime sends just the hash and variables - narrow the attack surface dramatically. Only registered queries run; ad-hoc queries are rejected. Apollo, Relay, and most clients support it. Mature GraphQL APIs in production use persisted queries; introspection becomes a build-time concern, not a runtime one.
Field-level authorization
The schema alone doesn't enforce authorization - a query for user.salary is valid GraphQL regardless of whether the caller can read salaries. Authorization has to live in the field resolvers (or in middleware that wraps them). This is where most GraphQL implementations bleed BOLA / BOPLA: the schema is correct, the per-field check is missing.
Apollo Federation security
Federation composes multiple GraphQL subgraphs behind one gateway. The gateway and subgraphs must mutually authenticate (typically mTLS or signed JWTs), and subgraphs should not be directly reachable from clients. Misconfigured federation often leaves subgraphs exposed as full GraphQL endpoints with no auth - query them directly and skip the gateway's policy. Audit subgraph network reachability.
gRPC-specific concerns
gRPC is HTTP/2 + protobuf with strict typing - most of the input-validation problems REST and GraphQL face are eliminated by the wire format. Different concerns dominate:
- Reflection in production. gRPC reflection is GraphQL introspection's cousin - clients can ask the server for its full service definition at runtime. Useful for developer tooling, dangerous in production for the same reasons. Disable in prod.
- No built-in rate limiting. gRPC's HTTP/2 streams multiplex on a single connection; a per-connection rate limit doesn't translate. Use an L7 gateway (Envoy, Kong) for cross-cutting rate limiting, or a Redis-backed token bucket in interceptors.
- Binary protocol opacity for WAFs. Most WAFs do not parse protobuf; they see opaque binary frames. Mitigations: terminate gRPC at an Envoy-based gateway that can apply protocol-aware filters, deploy the API security platform's gRPC parser, or run the WAF in front of a REST/gRPC-gateway translation layer.
- Authentication. gRPC supports per-call credentials (token in metadata) and channel credentials (TLS, optionally mTLS). The mature pattern is mTLS for the channel plus a short-lived bearer token in metadata for the call.
- Authorization in interceptors. gRPC interceptors are the right place to apply central authorization - pulled from OPA, Cedar, or an internal PDP. Don't write authz into each handler.
- Long-lived streams. Bidirectional and server-streaming RPCs create long-lived connections. Define max-stream durations, idle timeouts, and abandoned-stream cleanup. Long streams also complicate rate limiting and abuse detection.
SSRF prevention in APIs
SSRF - Server Side Request Forgery - is the bug where the server fetches a URL the attacker supplies. Common shapes: profile-picture URL fetch, webhook test endpoint, RSS importer, URL preview generator, OAuth callback URL, server-side image processing. The attacker's prize is access to the internal network - the metadata service, internal admin endpoints, the cloud's own APIs.
The classic targets
- AWS instance metadata service (IMDS).
169.254.169.254on EC2 returns temporary IAM credentials of the instance role. IMDSv2 (token-based, single-hop) is much harder to SSRF - require it explicitly. The 2019 Capital One breach was IMDSv1 SSRF. - Azure Instance Metadata Service. Also at
169.254.169.254, requires theMetadata: trueheader (a partial mitigation against trivial SSRF; not full). - GCP metadata server.
metadata.google.internal/169.254.169.254; requires theMetadata-Flavor: Googleheader, which provides similar partial mitigation. - Link-local and loopback.
127.0.0.0/8,::1,169.254.0.0/16in IPv4; the IPv6 equivalents. - Private RFC 1918 ranges.
10.0.0.0/8,172.16.0.0/12,192.168.0.0/16- your internal subnets. - Cloud-internal endpoints. Internal load balancers, internal-only APIs, the admin port of any service the attacker can name.
The defense pattern
- Allowlist by domain, not by deny-list. If you only need to fetch
*.partner.com, allow exactly that. Deny-listing IP ranges is fragile; allow-listing is the default-deny correct answer. - Resolve once, validate, then fetch. DNS rebinding attacks return a public IP to your allow-check and a private IP to the actual fetch. Resolve the hostname, validate the IP, then connect to the literal IP - not the hostname.
- Block link-local, loopback, and RFC1918 at the egress layer. Defense in depth - the SSRF that bypasses your application code still hits an egress proxy that rejects internal addresses.
- Use an egress proxy. Stripe's smokescreen and similar tools enforce centrally - every outbound call from the API service goes through a proxy that knows the allow-list, blocks metadata endpoints, and logs every fetch.
- Require IMDSv2 universally. Set
HttpTokens=requiredon every EC2 instance; enforce via SCP or AWS Config rule. This single change neuters most cloud-credentials-via-SSRF attacks. - Drop the response. If the application's only need is "does this URL exist?", make the HEAD request, don't return the body. Most SSRF that leaks credentials needs the response.
API discovery & inventory
The shadow-API problem is now a top-10 OWASP item (API9, Improper Inventory Management) because almost every org has it. Endpoints that exist in production but aren't in the inventory: old API versions you forgot to turn off, staging environments accessible from the internet, services your team didn't know about, endpoints behind a feature flag that's silently on.
Three discovery sources
- From traffic. Mirror or sample gateway logs, parse for distinct paths, methods, and auth shape. The most accurate source - if it's getting traffic, it exists. Caveat: endpoints that exist but aren't being called still escape.
- From code. Scan repos for route definitions in known frameworks (Express, FastAPI, Spring, Rails, Gin). Catches endpoints not yet deployed and ones not seeing traffic. Caveat: dynamic route registration is hard to scan.
- From the cloud. Enumerate API Gateway / APIM / Apigee resources across every account and subscription. Catches the "someone span up an API Gateway in a side account" case CSPMs increasingly do this natively.
Tooling
- API security platforms - Salt Security, Traceable, Noname Security (Akamai), Cequence, Wallarm. All build inventory primarily from passive traffic analysis (gateway logs, mirrored traffic, or in-line agent).
- CSPM / CNAPP - Wiz, Prisma Cloud, Orca, Defender for Cloud increasingly enumerate managed-API resources as part of the asset inventory.
- Code-first scanners - Escape, Akto, and some of the API security platforms scan source repos in addition to traffic.
Maturity goal: one inventory, deduplicated, that combines traffic, code, and cloud sources, with each entry mapped to an owning team and a documented data classification.
Runtime API security platforms
The new analyst category. Vendors that sit on or alongside API traffic, build a behavioral model, and detect the business-logic attacks WAFs miss. The major players:
- Salt Security - early mover, cloud-delivered, strong analytics-led approach.
- Traceable - distributed-tracing roots; deep east-west visibility plus north-south.
- Noname Security - acquired by Akamai in 2024; now integrated with Akamai's WAAP.
- Cequence - bot management heritage, strong on credential-stuffing and business-flow abuse.
- Akamai API Security - combination of Noname plus native Akamai capabilities.
- Wallarm - open-source roots (Wallarm FAST), cloud and on-prem.
- Imperva - WAAP + API security from a long-time WAF vendor.
- 42Crunch - spec-first / OpenAPI-centric; complementary to traffic-based platforms.
- Pynt - focused on dev-time API security testing (DAST for APIs).
What they catch
- Sequence-based abuse - login, password-reset, gift-card-balance enumeration patterns.
- BOLA at runtime - user accessing object outside their normal pattern.
- Shadow APIs - the inventory side.
- Sensitive-data exposure - when an endpoint starts returning more data than it used to.
- Drift from the OpenAPI spec - endpoints accepting fields that aren't documented.
The platforms differ in deployment model (mirror / in-line / agent), where on the path they sit (CDN, gateway, sidecar, eBPF), and how heavily they lean on traffic analytics vs spec-based checks. Most large API estates end up with the WAAP / WAF for noise, plus one API security platform for the business-logic layer.
API testing
The testing toolchain mirrors AppSec: SAST-equivalent (scan the OpenAPI spec), DAST-equivalent (hit the running API with attack patterns), and fuzzing.
DAST for APIs
- OWASP ZAP - open-source, API-aware via OpenAPI import.
- Burp Suite - the de facto AppSec tool; strong API testing via the Burp REST scanner and extensions.
- Pynt - runs in CI from your Postman/OpenAPI collection.
- StackHawk - DAST built for CI/CD, API-first.
- Detectify - external attack-surface management with API testing.
- Bright Security - AI-augmented DAST with API focus.
Fuzzing
- Postman + Newman - collection-driven, schema-aware fuzzing for REST APIs.
- RestFuzz - OpenAPI-driven REST fuzzer.
- RESTler (Microsoft) - stateful REST API fuzzer that infers operation dependencies from the spec.
- Schemathesis - property-based testing from OpenAPI and GraphQL schemas.
Contract testing
- Pact - consumer-driven contracts; pre-commit verification that consumer expectations match provider behavior.
- Dredd - OpenAPI-validated end-to-end test runner.
Run schema linting (Spectral) in PR; DAST + fuzzing in nightly CI; manual Burp / ZAP for major releases; third-party pentests for critical APIs annually.
Webhooks security
Webhooks are the inverse of an API call - your system receives unsolicited HTTP POSTs from a third party (Stripe, GitHub, SaaS integrations). The defenses inherit some, invert others:
- Signature verification. Provider signs the payload with a shared secret or asymmetric key; you verify. Stripe's webhook signature pattern is the industry reference. Verify before parsing - a forged unsigned payload should never reach your handler.
- Replay protection. Signature alone isn't enough - an attacker who captures a valid webhook can replay it. Include a timestamp in the signed payload, reject requests older than a few minutes, and track recently-seen idempotency keys to drop duplicates.
- Idempotency on retries. Webhook providers retry on non-2xx responses. Your handler must be idempotent - applying the same webhook twice produces the same outcome. Track
idempotency_keyor use a deduplication store. - Allowlist by source IP and signature. Many webhook providers publish their egress IP ranges. Use them as a coarse filter; signature is the fine filter.
- Authenticate yourself to the webhook target. If you are the provider, mTLS your webhook deliveries or sign them per the above pattern; never accept "we can't verify the source" as a configuration.
- Limit the response. Don't echo request data in the response; don't return verbose errors. The other side doesn't need them, and they're a SSRF leak.
Third-party API consumption (you're being attacked through APIs you call)
API10 in the 2023 list - Unsafe Consumption of APIs - formalizes a class of risk most teams under-weight. Your code calls a third-party API and trusts the response. The upstream provider gets compromised, returns malicious data, and you ingest it.
- Treat third-party responses as untrusted input. Same input-validation rules apply: validate against a schema you control, reject unexpected fields, enforce length and type limits.
- Watch for unsafe deserialization. The most common pattern is
pickle.loads(response.content)or equivalent - never deserialize unsigned data from anywhere. - Set timeouts on every outbound call. No default timeout in your HTTP client is a slow-loris attack waiting to happen if the upstream is slow or malicious.
- Use circuit breakers. Upstream failures shouldn't cascade - Hystrix-style libraries (now Resilience4j, gobreaker, etc.) trip after N consecutive failures and fail fast.
- Manage secrets carefully. Third-party API keys are bearer credentials. Store in a secrets manager (AWS Secrets Manager, Azure Key Vault, GCP Secret Manager, HashiCorp Vault); rotate; never log; never embed in code.
- Retry with exponential backoff. Tight retry loops on a transient upstream failure compound the incident. Library-supported backoff with jitter is the default.
- Vendor risk management. The GRC program tracks third-party API providers as subprocessors when they handle customer data; the security review is on the same cadence as any other vendor.
APIs in the service mesh (east-west)
This page is the north-south view - clients on the outside, your APIs on the inside, the gateway in the middle. The east-west view - service-to-service traffic inside the cluster - is the service mesh page. The two are complementary:
- North-south. Untrusted clients, OAuth-issued tokens, gateway enforcement, request-level rate limiting. Higher payload variability, lower trust.
- East-west. Mutually-authenticated services, mTLS by default, sidecar-enforced policy, much higher trust baseline but the same blast-radius rule: a compromised service shouldn't be able to call the whole cluster.
Where they overlap: a request that arrived at the gateway with a user identity needs to propagate that identity through the mesh so downstream services authorize against the user, not against the calling service. Token propagation, RFC 9068 JWT profile for OAuth, and SPIFFE-style workload identity are the connective tissue.
AWS, Azure, and GCP side-by-side
The native API security capabilities each cloud ships, reduced to a one-screen reference:
| Capability | AWS | Azure | GCP |
|---|---|---|---|
| API gateway | API Gateway (REST + HTTP), AppSync (GraphQL) | API Management (APIM) | Apigee, API Gateway |
| Auth integration | Cognito, IAM, Lambda authorizers, JWT authorizers | Entra ID, OAuth/OIDC, certificate auth, JWT validation | Identity Platform, IAP, custom token verification |
| Fine-grained authz | Verified Permissions (Cedar) | Custom + Entra ID / RBAC | IAM Conditions, custom |
| Rate limiting | API Gateway usage plans, WAF rate-based rules | APIM rate-limit policy, Front Door rate limiting | Apigee quota policy, Cloud Armor rate-based rules |
| Schema validation | Request validators (OpenAPI in import) | APIM validate-content policy | Apigee OASValidation policy |
| API security platform | None native (3rd party); WAF + Inspector | Microsoft Defender for APIs (in APIM) | Apigee Advanced API Security |
| WAF for APIs | AWS WAF + Bot Control + Fraud Control | Azure WAF on Front Door / App Gateway | Cloud Armor with reCAPTCHA Enterprise |
| Discovery | Across accounts via Config + custom; 3rd party CSPM/CNAPP | Defender for APIs auto-discovery within APIM | Apigee API Hub, SCC asset inventory |
| mTLS support | API Gateway custom domain w/ truststore (S3-hosted) | APIM client certificate auth | Apigee mTLS, Cloud Load Balancing mTLS |
| Observability | CloudWatch, X-Ray, API Gateway access logs | App Insights, APIM diagnostics, Log Analytics | Cloud Logging, Cloud Trace, Apigee analytics |
The native tools cover the basics well; the runtime business-logic detection is still where third-party API security platforms add the most value, especially for organizations whose APIs are the product (any B2B SaaS, fintech, healthtech, marketplace).
Maturity stages
A staging model that maps to the work most programs actually do:
Stage 1 - Tribal-knowledge
APIs exist; no shared inventory. Auth is whatever each team picked. Some have rate limits; others don't. OWASP API Top 10 is something the AppSec team has read; engineering hasn't. The first breach or close call kicks off the next stage.
Stage 2 - Documented
OpenAPI specs exist for the major APIs. The auth pattern is standardized (typically OAuth + JWT through an identity provider). Rate limits are deployed on the obvious endpoints (login, password reset, search). An owner is listed for each major API.
Stage 3 - Gated
An API gateway fronts every external API; schema validation is enforced; rate limits are per-user. Authentication is centralized. BOLA tests run in CI for every endpoint. Webhook signature verification is mandatory.
Stage 4 - Observed
An API security platform watches production traffic, builds the inventory, alerts on anomalies. Shadow APIs surface within days of going live. Drift from OpenAPI specs is detected. Cross-user object access is flagged.
Stage 5 - Defended
Authorization is architecturally enforced through a central PDP (OPA / Cedar / AuthZed); BOLA is structurally impossible. Persisted queries for GraphQL; reflection disabled for gRPC; introspection scoped. The API security platform's behavioral models drive auto-block decisions on the worst patterns. The program is part of the product roadmap, not a separate audit.
Common pitfalls
- Assuming the WAF covers APIs. WAFs catch L7 attack signatures; they don't catch business-logic abuse. The biggest API bugs (BOLA, BFLA, BOPLA, unrestricted resource consumption) are invisible to WAF rule sets.
- JWT trust without validation. Decoding the JWT and reading the claims without verifying the signature, audience, issuer, and expiration. A surprisingly common bug across libraries, especially in handlers that "just need the user id."
- No rate limit on auth endpoints. Login, password reset, MFA verification, token refresh - all need tighter rate limits than the rest of the API. Brute force is most painful where the rate-limiter is loosest.
- No schema validation. Endpoints accept any JSON, parse it permissively, and trust the result. Schema validation at the gateway eliminates an entire class of bugs and is a same-day implementation if you already have an OpenAPI spec.
- GraphQL introspection / gRPC reflection in production. Free recon for any attacker. Disable in prod; the dev experience can be preserved with persisted queries or a non-prod environment.
- Secrets in URLs. API keys, tokens, session ids in query strings - they end up in access logs, browser history, referer headers, and CDN caches. Put credentials in headers; rotate any that leaked into a URL.
- No inventory. Without a continuously-maintained inventory, you can't answer "is this endpoint still in use," "who owns it," "what data does it touch?" The shadow-API problem grows silently.
- BOLA in every codebase that hand-rolls authz. Every endpoint that takes an object id has to check ownership; the architectural fix is making the check happen automatically (scoped queries, central PDP), not relying on developer discipline.
- Webhook handlers that trust the body before verifying the signature. Parse-then-verify is wrong; verify-then-parse is correct. Same logic for replay timestamps.
- SSRF via "fetch a URL the user supplied." Every URL-import, image-from-link, webhook-test, RSS-importer feature is an SSRF candidate. Egress-proxy, allow-list, and IMDSv2 are the layered defense.
Further reading
OWASP & standards
- OWASP API Security Top 10 - 2023
- OWASP API Security Project
- RFC 8725 - JWT Best Current Practices
- RFC 6749 - OAuth 2.0
- OAuth 2.1 draft
- OpenID Connect Core 1.0
- OpenAPI 3.1 specification
- JSON Schema 2020-12
Authorization engines
- Open Policy Agent
- Cedar policy language
- AuthZed / SpiceDB
- Topaz
- Google's Zanzibar paper (the ReBAC reference)
Provider docs
- AWS API Gateway
- Azure API Management
- Apigee documentation
- AWS IMDSv2 enforcement
- Microsoft Defender for APIs
- Apigee Advanced API Security
Related CSOH pages
- Network security - the L3/L4 layer underneath your APIs.
- Service mesh security - east-west API security inside the cluster.
- Vulnerability management - CVE handling for API runtime dependencies.
- IAM & identity - the identity model APIs authenticate against.
- Zero trust - the architectural pattern that APIs live inside.
- CI/CD - where API security testing lives in the pipeline.
- Cloud pentesting - the offensive view of API exposure.
FAQ
Do I need an API security platform if I already have a WAF?
A WAF and an API security platform overlap on the surface and disagree underneath. A WAF inspects request payloads against signature and rule sets - SQLi, XSS, command injection, common bot patterns. It does not know what a logical user-id is, what "order #1247 belongs to user A" means, or that the same authenticated user just enumerated 8,000 customer accounts in two minutes. The OWASP API Top 10 is dominated by business-logic failures (BOLA, BFLA, BOPLA, unrestricted resource consumption) that look like perfectly legitimate traffic to a WAF. If you operate a non-trivial API, you generally need both: the WAF for layer-7 noise, the API security platform for the business-logic and inventory problems.
How is API authentication different from web-app authentication?
Web-app auth typically lands on a session cookie set after a login form; the browser carries the cookie automatically; CSRF protections and SameSite cookies manage cross-origin risk. API auth has to work for clients that aren't browsers - mobile apps, service-to-service calls, partner integrations, CI runners, IoT devices - so the credential is usually a bearer token (OAuth access token, JWT) or mutual TLS, transported in an Authorization header rather than a cookie. The hard parts shift: token issuance, scope design, key rotation, signature validation, and replay protection become the dominant concerns, with no browser-supplied default in your favor.
GraphQL vs REST - which is more secure?
Neither is inherently more secure; they fail in different shapes. REST's surface is many endpoints with structured per-endpoint controls - BOLA, BFLA, and over-fetching dominate. GraphQL's surface is one endpoint with arbitrary query shapes, so authorization has to be enforced at every field resolver, and the attacker can compose expensive queries (depth, breadth, batching) that a REST consumer would have to make as separate calls. The pragmatic answer: GraphQL needs depth/complexity limits, persisted queries in production, introspection disabled in prod, and field-level authorization. REST needs object-level checks on every read and consistent schema validation. Both need rate limiting tuned to their query economics.
What is BOLA and why does almost every API have it?
BOLA - Broken Object Level Authorization - is the bug where an endpoint correctly authenticates the caller and confirms they can call this kind of endpoint, but does not check whether the specific object referenced in the request belongs to that caller. /api/orders/1247 returns order 1247 regardless of whether the authenticated user owns it. The reason it's nearly universal: object ownership lives in business data, not in the auth middleware, so the check has to be written into every handler - and one missed handler is a complete data-disclosure bug. The defenses are architectural (scope all queries through an authorization layer that joins user-id with object-id) and detective (log every cross-user object access and alert on patterns).
Should I disable GraphQL introspection in production?
Yes for almost every internet-facing GraphQL API. Introspection lets a client query the entire schema - types, fields, arguments, deprecations - which is exactly the recon a credentialed or unauthenticated attacker uses to plan further queries. Keep introspection on in development; disable or scope it (behind an internal-only header, a feature flag, or auth role) in production. The same goes for gRPC reflection.
Is mTLS overkill for service-to-service APIs?
Not in 2026. For east-west service-to-service traffic in a Kubernetes or service-mesh environment, mTLS is now the default - Istio, Linkerd, Consul, and the managed meshes all bring it without per-app code changes. For north-south (gateway to backend, partner integrations), mTLS is heavier to operate (cert lifecycle, partner onboarding) but pays back when the data is sensitive enough that "a leaked bearer token is a breach." The decision is usually: mesh inside, OAuth/OIDC at the edge, optional mTLS at the edge for high-trust partners.
How do I find APIs I don't know exist?
Shadow APIs - endpoints that exist in production but aren't in the inventory - are the dominant API governance problem. Discovery happens three ways: from traffic (mirror or sample gateway logs, parse for distinct paths, methods, and authentication shape), from code (scan repos for route definitions in known frameworks), and from the cloud (enumerate API Gateway / APIM / Apigee resources across every account). API security platforms (Salt, Traceable, Noname/Akamai, Cequence, Wallarm) build inventory from passive traffic; CSPMs increasingly enumerate the cloud-managed APIs. The maturity goal is one inventory that combines all three sources, deduplicated, with each entry mapped to an owning team.
Where next
- Network security - the perimeter and segmentation layer your APIs ride on.
- Service mesh security - the east-west counterpart to this page's north-south view.
- Vulnerability management - patching the libraries your API runtime depends on.
- IAM & identity - the identity layer auth pulls from.
- Friday Zoom - API security, BOLA war stories, and OAuth pitfalls come up regularly. Drop in.