Why Validate API Requests — Complete Guide
In this tutorial, you'll learn Why Validate Requests. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.
Request validation is essential because every unvalidated input is a potential security vulnerability, data corruption risk, and source of runtime errors.
What You'll Learn
By the end of this lesson, you will identify the risks of insufficient validation, quantify the cost of validation failures, and build a security-first validation mindset.
Why It Matters
The OWASP Top 10 lists injection attacks as the number one web vulnerability. Proper validation prevents SQL Injection, XSS, Command Injection, and data corruption.
Real-World Use
Equifax's 2017 data breach (147 million records) was caused by unvalidated input in a web framework. Validation would have prevented the attack.
Security Threats Without Validation
flowchart TD
Input[User Input] --> NoValidation[No Validation]
NoValidation --> SQL[SQL Injection]
NoValidation --> XSS[Cross-Site Scripting]
NoValidation --> Command[Command Injection]
NoValidation --> Path[Path Traversal]
NoValidation --> Overflow[Buffer Overflow]
Injection Prevention
# injection_prevention.py
from typing import Any, Dict, List, Optional
class InjectionDetector:
def __init__(self):
self.patterns = {
"sql": ["'", "\"", ";", "--", "DROP", "SELECT", "UNION", "OR 1=1"],
"xss": ["<script>", "</script>", "onerror=", "javascript:"],
"command": ["&&", "||", "|", ";", "`", "$("],
"path": ["../", "..\\", "/etc/", "C:\\"],
}
def detect(self, value: str) -> List[str]:
threats = []
upper = value.upper()
for category, patterns in self.patterns.items():
for pattern in patterns:
if pattern.upper() in upper:
threats.append(f"{category}: contains '{pattern}'")
break
return threats
def sanitize(self, value: str) -> str:
import re
value = re.sub(r"['\";\\]", "", value)
value = value.replace("<script>", "").replace("</script>", "")
value = value.replace("..", "")
return value.strip()
detector = InjectionDetector()
inputs = [
"John",
"1; DROP TABLE users",
"<script>alert('xss')</script>",
"../../etc/passwd",
]
for inp in inputs:
threats = detector.detect(inp)
sanitized = detector.sanitize(inp) if threats else inp
status = "SAFE" if not threats else f"SANITIZED ({', '.join(threats)})"
print(f"Input: {inp:30s} -> {status:50s} -> '{sanitized}'")
Expected output:
Input: John -> SAFE -> 'John'
Input: 1; DROP TABLE users -> SANITIZED (sql: contains ''') -> '1 DROP TABLE users'
Input: <script>alert('xss')</script> -> SANITIZED (xss: contains '<script>') -> 'alert(xss)'
Input: ../../etc/passwd -> SANITIZED (path: contains '../') -> 'etc/passwd'
Data Integrity Validation
# data_integrity.py
from typing import Any, Dict, List, Optional
class IntegrityValidator:
def validate_order(self, order: Dict) -> List[str]:
errors = []
# Type validation
if not isinstance(order.get("quantity"), int):
errors.append("quantity must be integer")
elif order["quantity"] <= 0:
errors.append("quantity must be positive")
if not isinstance(order.get("price"), (int, float)):
errors.append("price must be number")
elif order["price"] <= 0:
errors.append("price must be positive")
# Range validation
if order.get("discount", 0) < 0 or order.get("discount", 0) > 100:
errors.append("discount must be 0-100")
# Format validation
if "email" in order and "@" not in order["email"]:
errors.append("invalid email format")
return errors
validator = IntegrityValidator()
orders = [
{"quantity": 2, "price": 29.99, "discount": 10, "email": "a@x.com"},
{"quantity": -1, "price": "free", "discount": 150, "email": "invalid"},
{},
]
for order in orders:
errors = validator.validate_order(order)
status = "VALID" if not errors else f"INVALID: {errors}"
print(f"Order {order}: {status}")
Expected output:
Order {'quantity': 2, 'price': 29.99, 'discount': 10, 'email': 'a@x.com'}: VALID
Order {'quantity': -1, 'price': 'free', 'discount': 150, 'email': 'invalid'}: INVALID: ['quantity must be positive', 'price must be number', 'discount must be 0-100', 'invalid email format']
Order {}: INVALID: ['quantity must be integer', 'price must be number']
Common Mistakes
1. Trusting Client-Side Validation
Client validation is for UX, not security. Server must validate everything because clients can be bypassed.
2. Validating Only Required Fields
Optional fields must also be validated. An optional email field should still be checked for format.
3. No Input Size Limits
Without length limits, an attacker can send a 1GB JSON payload and crash your server.
4. Error Messages Leaking Info
"Password incorrect for user admin" confirms the username exists. Use generic messages.
5. Not Validating All Sources
Validate body, query params, URL params, headers, and cookies. Any input source can be malicious.
Practice Questions
1. What is the most dangerous consequence of no validation?
Security vulnerabilities: SQL injection, XSS, command injection, path traversal.
2. Why is client-side validation not enough?
Clients can be modified or bypassed. Server validation is the only trusted validation.
3. What is input sanitization?
Cleaning input by stripping dangerous characters, trimming whitespace, and normalizing formats.
4. Should error messages reveal which field is invalid?
Yes, but be vague about the reason to avoid leaking information useful to attackers.
Challenge
Conduct a security audit of a sample API endpoint. List all input sources, identify validation gaps, and implement a comprehensive validation pipeline.
FAQ
Mini Project: Validation Audit Tool
# validation_audit.py
from typing import Dict, List
class ValidationAudit:
def __init__(self, endpoint: str):
self.endpoint = endpoint
self.inputs = ["body", "query", "params", "headers", "cookies"]
self.validated: List[str] = []
def mark_validated(self, source: str):
self.validated.append(source)
def report(self) -> Dict:
missing = [s for s in self.inputs if s not in self.validated]
coverage = f"{len(self.validated)}/{len(self.inputs)}"
return {
"endpoint": self.endpoint,
"coverage": coverage,
"validated": self.validated,
"missing": missing,
"secure": len(missing) == 0,
}
audit = ValidationAudit("POST /users")
audit.mark_validated("body")
audit.mark_validated("query")
print(audit.report())
Expected output:
{'endpoint': 'POST /users', 'coverage': '2/5', 'validated': ['body', 'query'], 'missing': ['params', 'headers', 'cookies'], 'secure': False}
What's Next
You understand why validation matters. Next, learn schema validation, then middleware validation.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro