Practical API Security

Security Intermediate 22 min Jan 12, 2026

Audience

This guide is for anyone who needs to understand API security fundamentals:

  • Backend developers building APIs that handle sensitive data
  • Frontend developers integrating with APIs and handling authentication
  • Security engineers reviewing API architectures for vulnerabilities
  • DevOps engineers configuring API gateways and security controls
  • Technical leads making security decisions for their teams

Basic knowledge of HTTP and REST APIs is assumed. If you need a refresher, start with How a REST API Works.

Goal

After reading this guide, you’ll understand:

  • What constitutes an API’s attack surface and where vulnerabilities hide
  • The OWASP API Security Top 10 threats and how they manifest
  • How to implement input validation to prevent injection attacks
  • What CORS actually protects (and what it doesn’t)
  • Why rate limiting matters and how it works conceptually
  • Which security headers every API should implement

You won’t become a penetration tester, but you’ll have the knowledge to build APIs that resist common attacks and pass security reviews.

1. Understanding API Attack Surface

Every API exposes a surface area that attackers can probe. Understanding this surface is the first step to defending it.

What is Attack Surface?

The attack surface is the sum of all points where an attacker can try to enter or extract data from your system. For APIs, this includes:

graph TB
    subgraph "API Attack Surface"
        A[Endpoints] --> A1["/users, /orders, /admin"]
        B[Data] --> B1["Request bodies, query params, headers"]
        C[Authentication] --> C1["Tokens, sessions, API keys"]
        D[Business Logic] --> D1["Workflows, state machines"]
        E[Infrastructure] --> E1["Servers, databases, caches"]
    end
    style A fill:#ffcdd2
    style B fill:#fff9c4
    style C fill:#c8e6c9
    style D fill:#bbdefb
    style E fill:#e1bee7

The Three Dimensions of API Risk

1. Endpoints (What you expose)

Every endpoint you create is a potential entry point. Consider:

  • How many endpoints does your API have?
  • Are there hidden or undocumented endpoints?
  • Do you have debug or admin endpoints in production?

2. Data (What flows through)

Data enters your API through:

  • Request bodies (JSON, XML, form data)
  • Query parameters (?search=...&limit=...)
  • Path parameters (/users/{id})
  • Headers (Authorization, custom headers)
  • Cookies

Each input channel is a potential injection vector.

3. Authentication/Authorization (Who can access what)

The most common API security failures involve:

  • Broken authentication (weak tokens, missing validation)
  • Broken authorization (accessing other users’ data)
  • Excessive data exposure (returning more than needed)

Real-World Example: The Attack Surface of a Banking API

Consider a simple banking API:

Banking API Endpoints http
POST   /auth/login           # Authentication
POST   /auth/refresh         # Token refresh
GET    /accounts             # List user accounts
GET    /accounts/{id}        # Account details
POST   /transfers            # Create transfer
GET    /transfers/{id}       # Transfer status
GET    /statements/{year}    # Annual statements

Attack surface analysis:

ComponentRiskExample Attack
/auth/loginCredential stuffing, brute forceTry millions of password combinations
/accounts/{id}IDOR (Insecure Direct Object Reference)Change id to access other accounts
/transfersBusiness logic abuseNegative transfer amounts
Query paramsInjection?year=2024; DROP TABLE accounts
JWT tokensToken forgery, algorithm confusionChange alg to none

Key insight: Security isn’t just about one endpoint. It’s about understanding how all pieces interact and where assumptions break down.

2. Common Threats: OWASP API Security Top 10

The OWASP API Security Top 10 catalogs the most critical API vulnerabilities. Let’s examine each one.

API1: Broken Object Level Authorization (BOLA)

The problem: Users can access resources belonging to other users by manipulating object IDs.

Example:

# User A requests their order
GET /orders/12345
Authorization: Bearer token_user_a

# User A changes the ID to access User B's order
GET /orders/12346
Authorization: Bearer token_user_a   # Same token, different order!

Why it happens: The API trusts the object ID without verifying the user owns that resource.

Prevention:

  • Always verify resource ownership before returning data
  • Use indirect references (user-specific IDs) instead of database IDs
  • Implement authorization checks at the data layer, not just the API layer

API2: Broken Authentication

The problem: Weak authentication mechanisms allow attackers to impersonate users.

Common failures:

  • Weak password policies
  • No rate limiting on login attempts
  • Tokens that don’t expire
  • Credentials in URLs (query strings)
  • Missing multi-factor authentication for sensitive operations

API3: Broken Object Property Level Authorization

The problem: Users can modify object properties they shouldn’t have access to.

Example:

Malicious Request json
// User tries to update their profile
PUT /users/me
{
  "name": "Alice",
  "email": "[email protected]",
  "role": "admin",          // Shouldn't be allowed!
  "verified": true          // Shouldn't be allowed!
}

Prevention: Explicitly define which fields users can modify. Never blindly accept all fields from the request.

API4: Unrestricted Resource Consumption

The problem: APIs don’t limit how many resources a user can consume.

Impacts:

  • Denial of Service (DoS)
  • Unexpected cloud bills
  • Database exhaustion

Examples:

  • No pagination limits (GET /users?limit=1000000)
  • Expensive operations without throttling (complex searches)
  • File uploads without size limits

API5: Broken Function Level Authorization

The problem: Users can access administrative functions they shouldn’t.

Example:

# Regular user discovers admin endpoint
DELETE /admin/users/12345
Authorization: Bearer regular_user_token

# API doesn't verify the user has admin privileges

Prevention: Implement role-based access control (RBAC) and verify permissions for every function.

API6: Unrestricted Access to Sensitive Business Flows

The problem: Attackers abuse legitimate features for malicious purposes.

Examples:

  • Creating thousands of accounts to abuse free trials
  • Automating purchase flows to scalp inventory
  • Scraping data at scale despite terms of service

API7: Server Side Request Forgery (SSRF)

The problem: The API fetches resources from user-supplied URLs, allowing access to internal systems.

Example:

// Legitimate request
POST /fetch-metadata
{
  "url": "https://example.com/image.png"
}

// Malicious request
POST /fetch-metadata
{
  "url": "http://169.254.169.254/latest/meta-data/"  // AWS metadata!
}

API8: Security Misconfiguration

The problem: Default configurations, unnecessary features, or missing security headers.

Examples:

  • Debug mode enabled in production
  • Default credentials unchanged
  • Unnecessary HTTP methods enabled (TRACE, OPTIONS)
  • Missing security headers (see Section 6)
  • Verbose error messages exposing internal details

API9: Improper Inventory Management

The problem: Organizations lose track of their APIs, leaving old versions exposed.

Common issues:

  • Old API versions still accessible (/v1/ alongside /v3/)
  • Development/staging APIs exposed to internet
  • Undocumented endpoints created by developers
  • Shadow APIs created by other teams

API10: Unsafe Consumption of APIs

The problem: Your API blindly trusts data from third-party APIs.

Example: Your API calls a partner API and inserts the response directly into your database without validation, allowing injection attacks through the third party.

3. Input Validation: Your First Line of Defense

Every piece of data entering your API is potentially malicious. Input validation is the practice of verifying that data meets expected formats before processing.

The Golden Rule

Never trust client input. Validate everything.

This includes:

  • Request bodies
  • Query parameters
  • Path parameters
  • Headers
  • File uploads

Types of Validation

1. Type validation: Is it the expected data type?

// Expected: number
// Received: "123; DROP TABLE users"
const userId = parseInt(req.params.id);
if (isNaN(userId)) {
  return res.status(400).json({ error: "Invalid user ID" });
}

2. Format validation: Does it match the expected pattern?

// Email format validation
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
if (!emailRegex.test(email)) {
  return res.status(400).json({ error: "Invalid email format" });
}

3. Range validation: Is the value within acceptable bounds?

// [Pagination](https://reference.apios.info/terms/pagination/) limit validation
const limit = Math.min(Math.max(parseInt(req.query.limit) || 20, 1), 100);
// limit will be between 1 and 100

4. Business logic validation: Does the value make sense?

// Transfer amount validation
if (amount <= 0) {
  return res.status(400).json({ error: "Amount must be positive" });
}
if (amount > account.balance) {
  return res.status(400).json({ error: "Insufficient funds" });
}

Schema Validation

Instead of validating each field manually, use schema validators like JSON Schema, Joi, Zod, or Yup:

Zod Schema Validation javascript
import { z } from 'zod';

const TransferSchema = z.object({
  fromAccount: z.string().uuid(),
  toAccount: z.string().uuid(),
  amount: z.number().positive().max(1000000),
  currency: z.enum(['USD', 'EUR', 'GBP']),
  description: z.string().max(200).optional()
});

// Usage
try {
  const validData = TransferSchema.parse(req.body);
  // Process validData safely
} catch (error) {
  return res.status(400).json({ error: error.errors });
}

Output Encoding

Validation prevents bad data from entering. Output encoding prevents bad data from causing harm when leaving:

// Don't return raw user input in HTML responses
// Bad:
res.send(`<p>Welcome, ${username}</p>`);

// Good:
import { escape } from 'html-escaper';
res.send(`<p>Welcome, ${escape(username)}</p>`);

What Validation Won’t Catch

Validation stops malformed data, but not:

  • Authorized users doing unauthorized things (BOLA)
  • Valid data used for malicious purposes (business logic abuse)
  • Attacks through other channels (social engineering)

Validation is necessary but not sufficient. Defense requires multiple layers.

4. CORS: What It Actually Protects

CORS (Cross-Origin Resource Sharing) is one of the most misunderstood security mechanisms. Let’s clarify what it does and doesn’t do.

The Same-Origin Policy

Browsers enforce the Same-Origin Policy: scripts on one origin can’t freely access resources from another origin.

Origin = Protocol + Domain + Port

URLOrigin
https://api.example.com/usershttps://api.example.com
https://api.example.com:443/usershttps://api.example.com (443 is default)
http://api.example.com/usershttp://api.example.com (different protocol)
https://app.example.com/https://app.example.com (different subdomain)

What CORS Actually Does

CORS is a relaxation of the Same-Origin Policy. It lets servers specify which origins can access their resources.

sequenceDiagram
    participant Browser
    participant Frontend as app.example.com
    participant API as api.example.com

    Browser->>Frontend: Load page
    Frontend->>Browser: JavaScript wants to call API
    Browser->>API: OPTIONS /users (Preflight)
    Note over Browser,API: "Can app.example.com access this?"
    API->>Browser: Access-Control-Allow-Origin: app.example.com
    Browser->>API: GET /users
    API->>Browser: Response data
    Browser->>Frontend: Data delivered

CORS Headers Explained

Server response headers:

CORS Response Headers http
# Which origins can access the resource
Access-Control-Allow-Origin: https://app.example.com

# Which HTTP methods are allowed
Access-Control-Allow-Methods: GET, POST, PUT, DELETE

# Which headers the client can send
Access-Control-Allow-Headers: Content-Type, Authorization

# Whether to include credentials (cookies, auth headers)
Access-Control-Allow-Credentials: true

# How long to cache the preflight response (seconds)
Access-Control-Max-Age: 86400

The Preflight Request

For “non-simple” requests (PUT, DELETE, custom headers, JSON body), browsers send a preflight OPTIONS request first:

OPTIONS /users [HTTP/1.1](https://reference.apios.info/terms/http-1-1/)
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: Content-Type, Authorization

The server must respond with appropriate CORS headers, or the browser blocks the actual request.

Common CORS Mistakes

Mistake 1: Allowing all origins

# DANGEROUS: Any website can make requests!
Access-Control-Allow-Origin: *

Problem: Any website can call your API from their JavaScript. Combined with Allow-Credentials: true, attackers can impersonate logged-in users.

Mistake 2: Reflecting the Origin header

# DANGEROUS: Echoing back whatever origin is sent
origin = request.headers.get('Origin')
response.headers['Access-Control-Allow-Origin'] = origin

Problem: This is equivalent to * but works with credentials, making it even more dangerous.

Mistake 3: Trusting null origin

# DANGEROUS: null origin can be spoofed
Access-Control-Allow-Origin: null

Problem: Attackers can send requests from sandboxed iframes with origin: null.

What CORS Doesn’t Protect

CORS only affects browsers. It doesn’t protect against:

  • Server-to-server requests (curl, Postman, backend services)
  • Mobile apps
  • Anyone who copies the request from browser DevTools

CORS is not authentication. It’s a browser policy that prevents unwitting users from being exploited by malicious websites.

Correct CORS Configuration

Express CORS Configuration javascript
const cors = require('cors');

const allowedOrigins = [
  'https://app.example.com',
  'https://admin.example.com'
];

app.use(cors({
  origin: (origin, callback) => {
    // Allow requests with no origin (mobile apps, curl)
    if (!origin) return callback(null, true);

    if (allowedOrigins.includes(origin)) {
      callback(null, true);
    } else {
      callback(new Error('Not allowed by CORS'));
    }
  },
  credentials: true,
  methods: ['GET', 'POST', 'PUT', 'DELETE'],
  allowedHeaders: ['Content-Type', 'Authorization']
}));

5. Rate Limiting: Protecting Against Abuse

Rate limiting restricts how many requests a client can make in a given time period. It’s essential for preventing abuse and ensuring fair resource usage.

Why Rate Limiting Matters

Without rate limiting, attackers can:

  1. Brute force authentication: Try millions of passwords
  2. Denial of Service: Overwhelm your servers with requests
  3. Scrape data: Download your entire database
  4. Exploit business logic: Create unlimited accounts, abuse promotions
  5. Generate massive bills: If you pay per API call to third parties

Rate Limiting Concepts

graph LR
    subgraph "Rate Limit Window"
        A[Request 1] --> B[Request 2]
        B --> C[Request 3]
        C --> D[...]
        D --> E[Request 100]
        E --> F[Request 101 - BLOCKED]
    end
    G[Window Resets] --> A
    style F fill:#ffcdd2
    style G fill:#c8e6c9

Key concepts:

TermDescriptionExample
LimitMaximum requests allowed100 requests
WindowTime period for countingper minute
IdentifierHow to identify the clientIP address, API key, user ID
QuotaThe remaining requests73/100 remaining

Common Rate Limiting Strategies

1. Fixed Window

  • Count requests in fixed time intervals
  • Simple but can allow bursts at window edges
  • Example: 100 requests per minute, resets at :00

2. Sliding Window

  • Smooths out the fixed window problem
  • More complex to implement
  • Example: 100 requests in any 60-second period

3. Token Bucket

  • Tokens replenish at a fixed rate
  • Allows bursts up to bucket size
  • Example: 10 tokens/second, bucket holds 100

4. Leaky Bucket

  • Requests processed at a fixed rate
  • Excess requests queue or drop
  • Smoothest traffic pattern

Rate Limit Headers

Standard headers to communicate rate limit status:

Rate Limit Response Headers http
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 73
X-RateLimit-Reset: 1641024000

# When limit exceeded
HTTP/1.1 [429 Too Many Requests](https://reference.apios.info/terms/429-too-many-requests/)
Retry-After: 60

What to Rate Limit

ResourceWhyExample Limit
Login attemptsPrevent brute force5 per minute per IP
Password resetPrevent enumeration3 per hour per email
API endpointsGeneral abuse prevention1000 per hour per user
Expensive operationsPrevent DoS10 per minute
File uploadsStorage abuse100 MB per day

Choosing Identifiers

How you identify clients affects security:

IdentifierProsCons
IP addressNo auth requiredShared IPs, VPNs, proxies
API keyClear accountabilityKey can be stolen
User IDAccurate per-userRequires authentication
CombinationMost accurateComplex to implement

Best practice: Use multiple identifiers. Rate limit by IP for unauthenticated endpoints, by user ID for authenticated ones.

Handling Rate Limit Exceeded

Return a helpful response:

429 Response Body json
{
  "error": "rate_limit_exceeded",
  "message": "Too many requests. Please retry after 60 seconds.",
  "retryAfter": 60,
  "limit": 100,
  "window": "1 minute",
  "documentation": "https://api.example.com/docs/rate-limits"
}

6. Security Headers: Essential HTTP Headers

HTTP headers provide a simple way to enable browser security features. Every API should implement these.

Essential Security Headers

1. Strict-Transport-Security (HSTS)

Forces browsers to use HTTPS, preventing downgrade attacks.

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload
  • max-age: How long to remember the policy (1 year recommended)
  • includeSubDomains: Apply to all subdomains
  • preload: Submit to browser preload lists

2. Content-Security-Policy (CSP)

Controls which resources the browser can load. Less relevant for JSON APIs, but critical if your API returns HTML.

Content-Security-Policy: default-src 'none'; frame-ancestors 'none'

3. X-Content-Type-Options

Prevents browsers from MIME-sniffing responses.

X-Content-Type-Options: nosniff

Why it matters: Without this, browsers might interpret JSON as HTML, enabling XSS attacks.

4. X-Frame-Options

Prevents your pages from being embedded in iframes (clickjacking protection).

X-Frame-Options: DENY

5. Cache-Control

Controls caching behavior. Critical for sensitive data.

# For sensitive responses
Cache-Control: no-store, no-cache, must-revalidate, private

# For public, cacheable responses
Cache-Control: public, max-age=3600

6. Content-Type

Always set the correct content type to prevent MIME confusion.

Content-Type: application/json; charset=utf-8

Complete Security Headers Example

Express Security Headers Middleware javascript
const helmet = require('helmet');

// Apply security headers
app.use(helmet({
  contentSecurityPolicy: {
    directives: {
      defaultSrc: ["'none'"],
      frameAncestors: ["'none'"]
    }
  },
  hsts: {
    maxAge: 31536000,
    includeSubDomains: true,
    preload: true
  },
  noSniff: true,
  frameguard: { action: 'deny' }
}));

// Custom headers for API responses
app.use((req, res, next) => {
  res.setHeader('Cache-Control', 'no-store');
  res.setHeader('Pragma', 'no-cache');
  next();
});

Headers to Avoid

Remove these headers as they expose information:

# Remove - reveals server technology
Server: Apache/2.4.41 (Ubuntu)
X-Powered-By: Express

# Remove - reveals versions
X-AspNet-Version: 4.0.30319

7. Putting It All Together: Security Checklist

Here’s a practical checklist for securing your API:

Authentication & Authorization

  • Use strong authentication (OAuth 2.0, JWT with proper validation)
  • Implement authorization checks for every endpoint
  • Verify resource ownership, not just authentication
  • Use short-lived tokens with refresh mechanisms
  • Implement multi-factor authentication for sensitive operations

Input & Output

  • Validate all input (type, format, range, business logic)
  • Use schema validation libraries
  • Sanitize/encode output to prevent injection
  • Return consistent error formats (JSON, not HTML)
  • Don’t expose internal details in error messages

Rate Limiting & Abuse Prevention

  • Rate limit all endpoints (especially auth)
  • Use appropriate identifiers (IP, user ID, API key)
  • Return helpful 429 responses with Retry-After
  • Monitor for abuse patterns

Transport Security

  • Use HTTPS everywhere (no HTTP)
  • Implement HSTS header
  • Use secure cookies (Secure, HttpOnly, SameSite)
  • Keep TLS configuration up to date

Headers

  • Set all security headers (HSTS, CSP, X-Content-Type-Options)
  • Remove information disclosure headers (Server, X-Powered-By)
  • Use correct Content-Type
  • Set appropriate Cache-Control

CORS

  • Whitelist specific origins (no wildcard with credentials)
  • Don’t reflect Origin header
  • Limit allowed methods and headers

8. What’s Next: Defensive API Security Course

This guide covered the fundamentals of API security: understanding attack surface, common threats, and essential controls. But there’s much more to learn:

Topics Covered in the Course

The Defensive API Security course dives deep into:

  • Hands-on threat modeling: How to systematically identify vulnerabilities in your APIs
  • Security testing techniques: Manual testing, automated scanning, fuzzing
  • WAF and API Gateway configuration: Real-world setup guides
  • Authentication deep-dive: OAuth 2.0 flows, JWT security, session management
  • Advanced rate limiting: Implementing token bucket, distributed rate limiting
  • Security monitoring: Logging, alerting, incident response
  • Compliance requirements: GDPR, PCI-DSS, SOC 2 implications for APIs

Why a Course?

Security requires practice, not just knowledge. The course includes:

  • Interactive labs where you attack and defend APIs
  • Real-world case studies from security breaches
  • Configuration templates for popular frameworks
  • Code review exercises
  • Assessment tools and checklists

Deepen your understanding with these related terms: