· Updated 2026-08-06

API Security Best Practices: How to Protect Your APIs from Attack in 2026

APIs are now the primary attack surface for most web and mobile applications. They expose data directly, bypass the browser's same-origin protections, and are consumed by clients that are harder to control than a server-rendered page.

Most teams secure them the way they secured web applications in 2015: a login form, session cookies, maybe some input validation. That approach misses the class of vulnerabilities that define modern API attacks — broken object authorisation, weak token handling, and abuse patterns that authentication alone cannot stop.

This guide covers the practical controls that actually work: from the OWASP API Security Top 10, to JWT implementation details, to rate limiting design, to what anomaly detection looks like in production.


Why API Security Has Changed

The shift to API-first architecture has moved risk in two directions simultaneously.

APIs expose more surface area than rendered pages. Every endpoint is a potential entry point. Every field is a potential injection target. Every authentication check that is missing is a direct path to data.

At the same time, API clients — mobile apps, SPAs, third-party integrations — are partially or fully outside your control. A mobile app client can be reverse engineered, modified, and used to call your API with crafted inputs that no legitimate user would send.

The IQCrafter security research on Flutter app reverse engineering and React Native app analysis demonstrated this precisely: API authentication schemes embedded in mobile app binaries can be extracted and replicated by anyone with the right tools and enough time. Server-side API security controls are the only reliable defence once the client has been compromised.


The OWASP API Security Top 10

OWASP maintains a dedicated API Security Top 10, distinct from the general OWASP Top 10. The 2023 edition reflects the attack patterns most commonly seen in real API breaches.

# Risk What it means
1 Broken Object Level Authorisation (BOLA) API returns data for any object ID without checking if the requester owns it
2 Broken Authentication Weak tokens, no expiry, improper validation
3 Broken Object Property Level Authorisation Endpoint returns or accepts more fields than the user should access
4 Unrestricted Resource Consumption No rate limits on expensive operations
5 Broken Function Level Authorisation Users can call admin functions by guessing endpoint names
6 Unrestricted Access to Sensitive Business Flows Bots can abuse flows like checkout, referral, or account creation
7 Server-Side Request Forgery (SSRF) API fetches attacker-supplied URLs from the server
8 Security Misconfiguration Default creds, open admin panels, verbose error messages
9 Improper Inventory Management Forgotten or undocumented API versions still running in production
10 Unsafe Consumption of APIs Your API trusts and processes data from third-party APIs without validation

Each of these is covered with practical controls below.


1. Authentication: Get the Fundamentals Right

Authentication is the first control, but implementing it correctly requires more than adding an Authorization header.

JWT implementation pitfalls

JWT (JSON Web Tokens) are the most common authentication mechanism for REST APIs. Used correctly, they are secure. Common misconfigurations make them a primary vulnerability source:

Algorithm confusion — the none attack:

Some JWT libraries accept alg: "none" in the token header, meaning they will accept an unsigned token as valid. Always pin the expected algorithm server-side and reject tokens that specify a different algorithm.

// Wrong — trusts whatever the token claims
jwt.verify(token, secret);

// Right — pin the algorithm explicitly
jwt.verify(token, secret, { algorithms: ['RS256'] });

Symmetric vs asymmetric signing:

HS256 uses the same secret for signing and verification. Any service that can verify a token can also issue one. For APIs consumed by multiple services, RS256 or ES256 (asymmetric) is safer: the private key signs tokens (held by the auth service only), and the public key verifies them (can be distributed to all services).

Token expiry:

Access tokens should be short-lived — 15 to 60 minutes. Use refresh tokens to issue new access tokens, and rotate refresh tokens on each use. A long-lived access token is effectively a password that never expires.

What strong authentication looks like

  • Short-lived JWT access tokens (RS256/ES256, 15–60 minute expiry)
  • Refresh token rotation on use with absolute maximum lifetime
  • Token binding to device attestation for high-value mobile APIs
  • Rate limiting on the token issuance endpoint (login, refresh)
  • Immediate token revocation capability (token blocklist or short-lived design)

2. Authorisation: BOLA Is the Most Common Real-World API Vulnerability

Broken Object Level Authorisation (BOLA — also called IDOR, Insecure Direct Object Reference) is the most commonly exploited API vulnerability in real breaches.

The pattern:

GET /api/v1/invoices/1042
Authorization: Bearer <valid_token_for_user_A>

→ Returns invoice 1042, which belongs to user B

User A is authenticated. The API validates the token. But it never checks whether user A owns or has permission to read invoice 1042.

The fix is a mandatory authorisation check at the data layer:

# Wrong — fetches whatever ID was requested
invoice = Invoice.get(id=invoice_id)

# Right — scopes the query to the requesting user
invoice = Invoice.get(id=invoice_id, owner_id=current_user.id)
if not invoice:
    raise PermissionError()

This check must happen in every query that references a user-owned resource. Relying on the client to send only IDs it should access is not a control — it is an assumption that attackers exploit by changing the ID value in the request.

Broken Function Level Authorisation is the admin-endpoint equivalent: an API endpoint that performs a privileged action (delete user, approve transaction, access audit log) that is accessible to any authenticated user because the server only checks authentication, not role.

Both require the same discipline: at every endpoint, check who the caller is AND what they are allowed to do on which specific resource.


3. Input Validation: Define What Valid Looks Like

APIs that accept any input and attempt to handle it downstream are injection targets. The defensive posture is an allow-list schema: define exactly what valid input looks like and reject anything that does not match before any business logic runs.

Define schemas for all inputs:

from pydantic import BaseModel, Field
from typing import Literal

class CreateOrderRequest(BaseModel):
    product_id: int = Field(gt=0)
    quantity: int = Field(ge=1, le=100)
    currency: Literal["USD", "EUR", "GBP"]
    # No free-form text fields that could carry injection payloads

SQL injection remains the most impactful injection class. The only reliable prevention is parameterised queries — never string-concatenate user input into SQL:

# Wrong
query = f"SELECT * FROM orders WHERE user_id = {user_id}"

# Right
query = "SELECT * FROM orders WHERE user_id = %s"
cursor.execute(query, (user_id,))

Mass assignment (Broken Object Property Level Authorisation) occurs when an API accepts and applies any field the client sends — including fields the user should not be able to set:

// Wrong — applies whatever the client sends
user.update(req.body);

// Right — explicit allow-list of updatable fields
const allowed = ['name', 'email', 'preferences'];
const update = pick(req.body, allowed);
user.update(update);

4. Rate Limiting and Abuse Prevention

An API without rate limits is an open invitation to scraping, credential stuffing, and resource exhaustion. Rate limiting must be implemented at multiple levels:

Level What to limit Typical threshold
IP address All requests 100–500 req/min
API key / user token All requests 60–200 req/min
Authentication endpoint Login attempts 5–10 per minute per IP
Sensitive business flows Account creation, checkout 10–30 per hour per IP
Expensive operations File upload, report generation 5–20 per hour per user

Return 429 Too Many Requests with a Retry-After header. Do not silently drop requests — the client needs to know to back off.

For sophisticated bot abuse (credential stuffing, inventory hoarding, referral abuse), rate limiting alone is insufficient. Combine it with:

  • Request fingerprinting: Legitimate mobile app requests have consistent User-Agent headers, request timing relative to UI actions, and consistent header order. Scripted clients often deviate on all three.
  • Device attestation: Use Android Play Integrity or Apple DeviceCheck to validate that requests originate from unmodified app builds on legitimate devices.
  • Anomaly detection: Flag sudden spikes, geographic anomalies (a user's session jumping between continents), and access patterns inconsistent with how real users use the product.

5. Transport Security

HTTPS is non-negotiable, but transport security extends beyond enabling TLS.

HSTS (HTTP Strict Transport Security):

Strict-Transport-Security: max-age=63072000; includeSubDomains; preload

This tells browsers to refuse all plain-HTTP connections to your domain for the specified duration, preventing protocol downgrade attacks.

Certificate pinning for mobile clients:

For high-sensitivity APIs consumed by mobile apps, certificate pinning on the client side prevents traffic interception via a rogue CA. Pin against the public key hash (SPKI), not the full certificate, so certificate renewals do not break the pin. For the limits of certificate pinning against local attackers with device access, see our Flutter hardening guide.

TLS configuration:

  • Minimum TLS 1.2; prefer TLS 1.3
  • Disable weak cipher suites (RC4, DES, 3DES)
  • Enable OCSP stapling
  • Validate your configuration with SSL Labs (A rating minimum)

6. GraphQL-Specific Security Considerations

GraphQL APIs have a distinct security profile from REST. The flexibility that makes GraphQL powerful also creates attack surface that REST does not have.

Risk GraphQL-specific concern Control
Introspection exposure Schema introspection reveals your entire data model to attackers Disable introspection in production
Query depth attacks Deeply nested queries can cause exponential resolver execution Enforce maximum query depth (e.g. 5 levels)
Batch query abuse GraphQL batching allows sending hundreds of queries in one request Rate limit by query count, not just request count
Field-level authorisation Every field on every type needs an authorisation check, not just top-level resolvers Audit resolver-level permissions exhaustively
N+1 query risk Poorly designed resolvers can trigger thousands of database queries Use DataLoader or equivalent batching

7. Logging, Monitoring, and Anomaly Detection

You cannot defend what you cannot see. Comprehensive logging is a prerequisite for detecting attacks in progress.

Log every request with:

  • Timestamp (UTC, millisecond precision)
  • HTTP method and endpoint path
  • Authenticated user ID (never log the token itself)
  • IP address and User-Agent
  • Response status code and response time
  • Request size

Never log:

  • Token values or API keys
  • Passwords or credentials
  • Full request bodies that may contain PII
  • Credit card numbers, SSNs, or equivalent sensitive data

Alert on:

  • Repeated 401/403 on different resource IDs from the same IP (BOLA probing)
  • Authentication failures exceeding threshold within a time window (credential stuffing)
  • Request volume spikes from new IPs or user agents
  • Access to deprecated or undocumented API endpoints (inventory discovery)
  • Unusual response sizes (large responses may indicate data exfiltration)

8. API Inventory and Versioning Security

API 9 in the OWASP Top 10 — Improper Inventory Management — is underestimated. Old API versions that are no longer actively maintained but still running in production often lack the security controls added to current versions. Attackers find them through crawling, previous app versions, and leaked documentation.

Controls:

  • Maintain a complete, current inventory of all API endpoints and versions
  • Set explicit end-of-life dates for deprecated API versions and enforce them
  • Use API gateway routing to ensure all traffic passes through current security controls regardless of which version the client requests
  • Run secret scanning across repositories to detect leaked API keys or credentials

Security Testing Checklist

Use this as a structured baseline before a production API launches or after significant changes:

  • Authentication required on every non-public endpoint
  • Object-level authorisation checked on every endpoint that references a user-owned resource by ID
  • Function-level authorisation checked on every admin or privileged endpoint
  • JWT signed with RS256/ES256, expiry ≤ 60 minutes, algorithm pinned server-side
  • All inputs validated against an explicit schema before business logic runs
  • SQL and NoSQL queries use parameterised statements only
  • Mass assignment prevented by explicit field allow-lists
  • Rate limiting active on auth endpoints, sensitive flows, and expensive operations
  • HTTPS enforced with HSTS; TLS 1.2+ only
  • GraphQL introspection disabled in production; query depth limits active
  • All requests logged with user ID, IP, endpoint, and response code
  • Alerts configured for auth failure spikes, BOLA probing, and traffic anomalies
  • API inventory current; deprecated versions retired or behind the same gateway controls
  • Security headers set (CSP, X-Content-Type-Options, X-Frame-Options)

The Bigger Picture: The Server Cannot Trust the Client

The most important shift in API security thinking is accepting that the client is not trustworthy.

A mobile app client can be reverse engineered. An authentication scheme embedded in a binary can be extracted and replicated. A JWT decoded from an intercepted session can be studied for structure. Any security control that depends on the client behaving as intended will eventually be bypassed by an attacker with enough time and the right tools.

Server-side controls — authorisation at the data layer, rate limiting, anomaly detection, token binding to attestation — are the only controls that hold when the client is compromised. The client-side controls (certificate pinning, root detection, obfuscation) raise the cost of attack; the server-side controls are what remain when those costs are paid.


If you want a professional assessment of your API security posture — including what an attacker with reverse engineering tools would find — get in touch or explore our Mobile Security & Reverse Engineering service. We can also run a free Mobile API Exposure Snapshot on your mobile application to surface the most critical risks with no commitment.

Need Expert Guidance?

Planning custom software for your business?

Book a free consultation with our team to discuss architecture, product strategy, and the right build approach for your goals.

Book Free Consultation