Skip to main content

API Security Checklist (OWASP API Top 10 - 2023)

Authentication mechanics (OAuth2, JWT, mTLS) live in API Authentication & Security and API Security. This page maps design-level risks to concrete controls and shows how to express them in the OpenAPI contract.

#RiskOne-line causePrimary control
API1Broken Object Level Authorization (BOLA)GET /orders/42 doesn't check that order 42 belongs to the callerPer-object authorization on every access
API2Broken AuthenticationWeak tokens, no rate limit on login, JWT misvalidationStandard flows, short-lived tokens, strict validation
API3Broken Object Property Level AuthorizationExcessive data exposure + mass assignmentExplicit DTOs; per-field authorization
API4Unrestricted Resource ConsumptionNo limits on size, rate, costQuotas, pagination caps, timeouts, cost limits
API5Broken Function Level Authorization (BFLA)Regular user can call /admin/*Deny-by-default, role/scope checks per operation
API6Unrestricted Access to Sensitive Business FlowsBots abuse purchase/coupon/signup flowsBehavioural limits, device attestation, step-up
API7Server-Side Request ForgeryAPI fetches user-supplied URLsAllow-lists, egress controls
API8Security MisconfigurationOpen Swagger UI, verbose errors, permissive CORSHardened defaults, config-as-code scanning
API9Improper Inventory ManagementOld /v1 or shadow endpoints still liveAPI catalog, deprecation, gateway-only exposure
API10Unsafe Consumption of APIsTrusting third-party API responsesValidate/limit upstream data, timeouts, TLS

API1 - BOLA (the #1 API vulnerability)

// VULNERABLE: authenticated, but any user can read any order by guessing ids
@GetMapping("/orders/{id}")
Order get(@PathVariable String id) { return repo.findById(id).orElseThrow(); }

// FIXED: authorization is part of the query (also avoids loading then checking)
@GetMapping("/orders/{id}")
Order get(@PathVariable String id, @AuthenticationPrincipal Jwt jwt) {
return repo.findByIdAndCustomerId(id, jwt.getSubject())
.orElseThrow(() -> new OrderNotFoundException(id)); // 404, not 403: don't reveal existence
}
  • Don't rely on unguessable ids as security (they help, but are not authorization).
  • Centralise with method security: @PreAuthorize("@orderAuth.canRead(#id, authentication)").
  • Multi-tenant: put tenant_id in every query (and use Postgres RLS as defence in depth).
  • Tests: for every endpoint with an id, test user A cannot access user B's object (automated in CI).

API3 - Mass Assignment & Excessive Data Exposure

// VULNERABLE: binding request JSON directly onto the entity lets clients set role/balance/id
@PutMapping("/users/me")
User update(@RequestBody User body) { return repo.save(body); }

// FIXED: dedicated request DTO with only writable fields; map explicitly
public record UpdateProfileRequest(@Size(max = 80) String displayName, @Email String email) {}

@PutMapping("/users/me")
UserResponse update(@Valid @RequestBody UpdateProfileRequest body, @AuthenticationPrincipal Jwt jwt) {
return mapper.toResponse(service.updateProfile(jwt.getSubject(), body));
}

Never return entities (password hashes, internal flags, other users' PII). Use response DTOs; in OpenAPI mark readOnly fields and keep additionalProperties: false on request schemas so unknown fields are rejected: spring.jackson.deserialization.fail-on-unknown-properties=true for requests.

API4 - Resource Consumption

ControlWhere
Request body size limit (server.tomcat.max-http-form-post-size, spring.servlet.multipart.max-file-size, gateway client_max_body_size)Edge + app
maxItems, maxLength, maximum in OpenAPIGateway validation + app
Page size cap (limit <= 100)App
Rate limits per key/user/IP, with burst + sustainedGateway (algorithms)
Timeouts everywhere (client, server, DB, downstream)App + infra
Query cost limits (GraphQL depth/complexity, batch size, expansion depth)App
Async job quotas, concurrency caps per tenantApp
Expensive third-party calls (SMS/email) metered per accountApp

API5 - Function-Level Authorization in the Contract

paths:
/admin/users/{id}:
delete:
operationId: deleteUserAsAdmin
security:
- oauth2: [admin:users:delete] # scope required, enforced by gateway AND service
@Bean
SecurityFilterChain api(HttpSecurity http) throws Exception {
return http
.authorizeHttpRequests(a -> a
.requestMatchers("/actuator/health/**").permitAll()
.requestMatchers(HttpMethod.GET, "/api/v1/orders/**").hasAuthority("SCOPE_orders:read")
.requestMatchers("/api/v1/orders/**").hasAuthority("SCOPE_orders:write")
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.anyRequest().denyAll()) // deny by default
.oauth2ResourceServer(o -> o.jwt(Customizer.withDefaults()))
.build();
}

The scope list in the spec is documentation and input for gateway policy generation; keep them identical by testing that every operation in openapi.yaml has a security entry (Spectral operation-security-defined).

API8 - Misconfiguration Checklist

  • Swagger UI / /v3/api-docs / /actuator/* not publicly exposed
  • TLS 1.2+ only, HSTS, no HTTP; internal service-to-service via mTLS
  • CORS: explicit origins (no * with credentials), explicit methods/headers, Access-Control-Max-Age set
  • Generic error messages; no stack traces, SQL, framework banners (server.error.include-stacktrace=never)
  • Security headers: X-Content-Type-Options: nosniff, Cache-Control: no-store on sensitive responses, Content-Security-Policy for any HTML
  • Unused HTTP methods disabled (TRACE, unneeded OPTIONS)
  • Secrets in a vault, not in repo/env dumps/spec examples
  • Dependencies scanned (SCA) and base images patched

API7 - SSRF

If the API fetches a user-provided URL (webhooks, image import, link previews): resolve DNS yourself, reject private/loopback/link-local/metadata ranges (169.254.169.254), restrict schemes (https), disable redirects or re-validate each hop, enforce size/time limits, and run from an egress-restricted network segment.

API9 - Inventory

Maintain an API catalog with owners and lifecycle; enumerate gateway routes vs spec paths and alert on undocumented routes (shadow APIs) and zombie versions; remove debug/test endpoints before release; expose only through the gateway.

Security Testing in the Pipeline

StageToolFinds
LintSpectral security rulesMissing security, http:// servers, unbounded inputs
SASTSemgrep/CodeQLMissing authz checks, injection
Contract fuzzSchemathesis500s, schema violations, auth bypass hints
DASTOWASP ZAP (import OpenAPI)Injection, misconfig
Authz testsCustom (two-user BOLA matrix)API1/API5
RuntimeWAF + anomaly detection on gatewayAbuse, scraping

Next: API Design Interview Questions.

πŸ“–
Track Page Progress0 / 635 Read
Knowledge Base Completion0%