HOW-TO GUIDE | APPSEC
11 min read

How to Secure API Endpoints: The OWASP API Top 10 With Practical Fixes

94%
Of organizations experienced API security problems in 2024 (Salt Security)
#1
Broken object-level authorization (BOLA): the most frequently exploited API vulnerability category
3x
API traffic growth rate compared to web traffic growth: expanding attack surface faster than defenses
40%
Of companies say they do not know which of their APIs expose sensitive data (Noname Security)

SponsoredRetool

Retool's new app builder is where AI-generated code ships safely

Building apps with AI is easy. Getting them to production safely is another story.

Start building for free today

APIs have replaced forms as the primary way applications exchange data: and attackers have followed. API breaches look different from web application breaches: instead of SQL injection or XSS, API attackers exploit authorization logic, enumerate object IDs, and abuse legitimate business flows at scale.

The OWASP API Security Top 10 (2023) identifies the specific vulnerability categories that cause the majority of API-related data breaches. This guide covers the five highest-impact categories with the specific code patterns that create each vulnerability and the concrete fix.

API1: Broken Object-Level Authorization (BOLA)

What it is: The API accepts a user-supplied resource identifier (object ID) and returns the resource without verifying that the requesting user is authorized to access that specific object.

Vulnerable pattern:

# Flask example: BOLA vulnerability
@app.route('/api/orders/<order_id>')
def get_order(order_id):
    order = db.query('SELECT * FROM orders WHERE id = ?', order_id)
    return jsonify(order)  # No check: does this user own this order?

An attacker who knows their own order ID (say, 10042) simply increments: 10043, 10044, 10045... Each responds with a different customer's order data.

The fix:

@app.route('/api/orders/<order_id>')
@login_required
def get_order(order_id):
    # Always scope the query to the authenticated user
    order = db.query(
        'SELECT * FROM orders WHERE id = ? AND user_id = ?',
        order_id, current_user.id  # Add user ownership check
    )
    if not order:
        abort(404)  # Return 404 (not 403) to avoid confirming object existence
    return jsonify(order)

The rule: Every database query that fetches a user-specific resource must include the authenticated user's ID in the WHERE clause. Authorization is not a layer on top of data retrieval: it is part of data retrieval.

API2: Broken Authentication and API3: Broken Object Property Level Authorization

API2: Broken Authentication

APIs commonly implement authentication incorrectly: accepting expired or invalid JWTs, not validating JWT signatures, allowing login without rate limiting (enabling brute force), or sending tokens in URL parameters (which end up in server logs).

JWT validation: the full checklist:

import jwt

def verify_token(token):
    try:
        payload = jwt.decode(
            token,
            SECRET_KEY,
            algorithms=['HS256'],  # Specify explicitly: never accept 'none'
            options={
                'require': ['exp', 'iss', 'sub'],  # Require expiry, issuer, subject
                'verify_exp': True,  # Verify not expired
            }
        )
        if payload['iss'] != 'https://api.yourcompany.com':  # Verify issuer
            raise ValueError('Invalid issuer')
        return payload
    except jwt.ExpiredSignatureError:
        abort(401)
    except jwt.InvalidTokenError:
        abort(401)

Never accept the alg: none header: it bypasses signature verification entirely. Always specify the accepted algorithm list explicitly.

API3: Broken Object Property Level Authorization (Mass Assignment)

APIs that bind request body directly to database objects allow attackers to set properties they should not be able to set.

Vulnerable pattern:

# Express.js: mass assignment vulnerability
app.put('/api/users/:id', (req, res) => {
    User.findByIdAndUpdate(req.params.id, req.body)  // blindly applies all request fields
})
// An attacker sends: {"name": "Alice", "role": "admin", "plan": "enterprise"}

The fix:

# Allowlist only the properties the endpoint is permitted to update
const UPDATABLE_FIELDS = ['name', 'email', 'phone']

app.put('/api/users/:id', requireAuth, (req, res) => {
    const allowedUpdates = {}
    UPDATABLE_FIELDS.forEach(field => {
        if (req.body[field] !== undefined) allowedUpdates[field] = req.body[field]
    })
    User.findByIdAndUpdate(req.params.id, allowedUpdates)
})
Free daily briefing

Briefings like this, every morning before 9am.

Threat intel, active CVEs, and campaign alerts, distilled for practitioners. 50,000+ subscribers. No noise.

API4: Unrestricted Resource Consumption (Rate Limiting)

What it is: APIs without rate limiting allow attackers to send unlimited requests, enabling credential brute force, enumeration attacks, scraping, and denial of service.

The fix: three layers of rate limiting:

Layer 1: Per-IP rate limiting at the API gateway or reverse proxy:

# Nginx rate limiting
limit_req_zone $binary_remote_addr zone=api:10m rate=60r/m;

server {
    location /api/ {
        limit_req zone=api burst=20 nodelay;
        limit_req_status 429;
    }
}

Layer 2: Per-user/per-account rate limiting in application code:

from flask_limiter import Limiter
from flask_limiter.util import get_remote_address

limiter = Limiter(app, key_func=get_remote_address)

@app.route('/api/login', methods=['POST'])
@limiter.limit('5 per minute; 20 per hour')  # 5 login attempts per minute, 20 per hour
def login():
    ...

@app.route('/api/data')
@limiter.limit('100 per minute')  # Per-IP for data endpoints
def get_data():
    ...

Layer 3: Response headers for client transparency:

# Add rate limit headers to every response
response.headers['X-RateLimit-Limit'] = '100'
response.headers['X-RateLimit-Remaining'] = str(remaining_requests)
response.headers['X-RateLimit-Reset'] = str(reset_timestamp)

Authentication endpoints require stricter limits than data endpoints. A POST to /api/login should be limited to 5-10 attempts per minute from a single IP, with a 15-minute backoff after 10 failures. Data endpoints can be more permissive (100-200/minute) if requests are authenticated.

API5 and API6: Function-Level Authorization and Business Flow Abuse

API5: Broken Function-Level Authorization

APIs that expose admin functions without verifying the requesting user has admin permissions: often by checking role at the route level but missing it on specific endpoints.

Vulnerable pattern:

@app.route('/api/admin/users', methods=['GET'])
@login_required  # Only checks: is user logged in
def list_all_users():
    return jsonify(db.query('SELECT * FROM users'))  # Returns all users, no role check

The fix:

@app.route('/api/admin/users', methods=['GET'])
@require_role('admin')  # Role-specific decorator, not just login_required
def list_all_users():
    return jsonify(db.query('SELECT * FROM users'))

The systematic fix: audit every API endpoint against your permission matrix. Every endpoint that performs an administrative function should have a role requirement, and that role requirement should be enforced by a decorator or middleware: not inline code that could be accidentally omitted.

API6: Unrestricted Access to Sensitive Business Flows

APIs that implement business logic (checkout, promo code redemption, account creation) without rate limiting or anomaly detection allow automation of actions that should be rate-constrained.

Examples:

  • Gift card balance check endpoint with no rate limit → attackers enumerate gift card codes
  • Promo code redemption with no per-account limit → attackers apply the same promo code to many accounts
  • Account creation with no CAPTCHA or rate limit → attackers create thousands of accounts for credential stuffing

The fix requires business-logic-aware rate limiting:

@app.route('/api/promo-code/apply', methods=['POST'])
@login_required
@limiter.limit('3 per day', key_func=lambda: current_user.id)  # Per-USER limit, not per-IP
def apply_promo_code():
    ...

Per-IP limits do not protect against distributed abuse from many IPs. Business logic endpoints need per-account limits, transaction velocity checks, and anomaly detection for automated patterns.

API Security Checklist: Key Controls

A consolidated checklist for API security review:

Authentication:

  • All API endpoints require authentication (except explicitly public endpoints)
  • JWTs validate signature, expiry, issuer, and subject
  • Authentication endpoints rate-limited to 5-10 attempts/minute per IP
  • API keys are not transmitted in URL query parameters (use Authorization header)
  • No secrets committed to source code (see the GitHub secrets guide)

Authorization:

  • Every resource query includes the authenticated user's ID in the WHERE clause
  • Admin endpoints use role-specific decorators, not just login_required
  • Mass assignment prevented by field allowlisting in all update endpoints
  • Indirect object references (user-supplied IDs) are validated against the authenticated user

Input validation and output filtering:

  • All user input is validated: type, format, length, and range
  • API responses do not include fields the caller is not authorized to see
  • Error messages do not leak stack traces, database queries, or internal paths
  • HTTP methods are restricted per endpoint (GET for reads, POST/PUT for writes)

Rate limiting and monitoring:

  • All endpoints rate-limited (authentication endpoints: strict; data endpoints: moderate)
  • Rate limit headers returned on all responses
  • 429 responses logged and alerted on high volumes
  • API inventory maintained: every endpoint documented with its authentication requirement

The bottom line

API security failures are predominantly authorization failures, not authentication failures. Broken Object Level Authorization (always scope queries to the authenticated user's ID), broken function-level authorization (role-specific decorators on every admin endpoint), and mass assignment (field allowlisting in all update handlers) account for the majority of exploitable API vulnerabilities. Implement rate limiting on all endpoints: strictest on authentication, moderate on data: and maintain an API inventory documenting every endpoint and its authentication requirement.

Frequently asked questions

What is the most common API security vulnerability?

Broken Object Level Authorization (BOLA), also called IDOR (Insecure Direct Object Reference), is the most commonly exploited API vulnerability. It occurs when an API accepts a user-supplied resource ID and returns that resource without verifying the requesting user is authorized to access it. The fix is to always include the authenticated user's ID in every database query that retrieves user-specific data.

How do I secure a REST API?

Five essential controls: require authentication on all non-public endpoints; scope every database query to the authenticated user's identity (prevents BOLA); use role-specific decorators on admin endpoints; implement rate limiting on all endpoints with stricter limits on authentication routes; and allowlist only permitted fields in update request handlers to prevent mass assignment.

What is the OWASP API Security Top 10?

The OWASP API Security Top 10 lists the most critical API vulnerabilities: API1 Broken Object Level Authorization (BOLA/IDOR), API2 Broken Authentication, API3 Broken Object Property Level Authorization (mass assignment + excessive data exposure), API4 Unrestricted Resource Consumption (rate limiting absent), API5 Broken Function Level Authorization (horizontal vs vertical access control), API6 Unrestricted Access to Sensitive Business Flows (automation abuse), API7 Server Side Request Forgery (SSRF), API8 Security Misconfiguration, API9 Improper Inventory Management (undocumented/shadow APIs), and API10 Unsafe Consumption of APIs (trusting third-party API responses without validation). The current version was published in 2023.

How do I prevent API rate limiting bypass?

Attackers bypass rate limiting by rotating IP addresses, user agents, or API keys. Effective rate limiting must track multiple dimensions: per-IP, per-user, and per-API-key limits enforced in combination. Apply stricter limits on authentication endpoints (e.g., 5 password attempts per account per 15 minutes) with account lockout. Use a centralized rate limiting layer (API gateway or middleware) rather than per-endpoint implementation. For critical actions, use CAPTCHA challenges after threshold hits rather than hard blocks, to avoid denial of service against legitimate users.

What is the difference between API authentication and API authorization?

Authentication verifies who is making the request: typically via JWT bearer token, API key, or OAuth 2.0 access token. Authorization determines what the authenticated caller is permitted to access. Both are required. A common API security failure is strong authentication (valid JWT required) combined with broken authorization (any authenticated user can access any other user's data). Object-level authorization must verify that the authenticated user's ID matches the owner of the requested resource in every data access operation: authentication alone does not prevent BOLA attacks.

How do I build and maintain an API security inventory?

An API inventory is the foundation of API security: you cannot protect endpoints you do not know exist. Shadow APIs (undocumented or forgotten endpoints) are frequently the entry point for successful API attacks. Build your inventory using three sources: your API gateway or service mesh logs (extract every unique route pattern), static code analysis tools that parse your codebase for route definitions, and network traffic analysis using a tool like Postman, Burp Suite, or AWS API Gateway's access logs to surface endpoints receiving live traffic. Document for each endpoint: its authentication requirement, the data it reads or writes, its rate limiting configuration, and the team responsible for it. Run quarterly reconciliation between your discovered endpoint list and your documented inventory: any endpoint present in traffic logs but absent from documentation is a shadow API that needs review. OWASP API9 (Improper Inventory Management) specifically calls out undocumented and deprecated APIs as a top vulnerability source: old API versions left running after a new version ships are a frequent attacker target because they often lack the security controls added to newer versions.

Sources & references

  1. OWASP API Security Top 10 2023
  2. Salt Security: 2025 State of API Security
  3. CISA: Defending APIs

Free resources

25
Free download

Critical CVE Reference Card 2025–2026

25 actively exploited vulnerabilities with CVSS scores, exploit status, and patch availability. Print it, pin it, share it with your SOC team.

No spam. Unsubscribe anytime.

Free download

Ransomware Incident Response Playbook

Step-by-step 24-hour IR checklist covering detection, containment, eradication, and recovery. Built for SOC teams, IR leads, and CISOs.

No spam. Unsubscribe anytime.

Free newsletter

Get threat intel before your inbox does.

50,000+ security professionals read Decryption Digest for early warnings on zero-days, ransomware, and nation-state campaigns. Free, daily, no spam.

Unsubscribe anytime. We never sell your data.

Eric Bang
Author

Founder & Cybersecurity Evangelist, Decryption Digest

Cybersecurity professional with expertise in threat intelligence, vulnerability research, and enterprise security. Covers zero-days, ransomware, and nation-state operations for 50,000+ security professionals every morning.

Black Hat Giveaway

Win a $2,495 Black Hat pass.

Full-access to Black Hat USA 2026 in Las Vegas. Subscribe free to enter.

Joins Decryption Digest daily briefing. Unsubscribe anytime.

Giveaway: Black Hat USA 2026 Full-Access Pass ($2,495 value)

Details →
Daily Briefing

Subscribe to enter the giveaway

Every subscriber is automatically entered. You also get daily threat intel every morning: zero-days, ransomware, and nation-state campaigns. Free. No spam.

Already subscribed? You're already entered.

Giveaway

Win a $2,495 Black Hat USA 2026 pass.