XSS Protection for APIs — Complete Cross-Site Scripting Guide
In this tutorial, you will learn about XSS Protection for APIs. We cover key concepts, practical examples, and best practices to help you master this topic.
Cross-Site Scripting (XSS) allows attackers to inject malicious scripts into web pages viewed by other users. For APIs, XSS occurs when user-supplied data is returned without proper encoding.
What You'll Learn
You'll learn the three types of XSS (reflected, stored, DOM-based) and how to prevent them through API-level defenses.
Why It Matters
XSS is the second most common web vulnerability. It enables Session Hijacking, credential theft, defacement, and malware distribution. An XSS vulnerability in your API affects every user of your application.
Real-World Use
A forum API returns user posts without encoding. An attacker posts a comment containing a script that steals session cookies. Every user who views the page unknowingly sends their session token to the attacker.
flowchart LR
subgraph Stored XSS
A[Attacker] --> B[Post Malicious Comment]
B --> C[Database]
C --> D[API Returns Unencoded]
D --> E[User Browser Executes Script]
E --> F[Cookies Sent to Attacker]
end
subgraph Prevention
G[Input Sanitization]
H[Output Encoding]
I[CSP Headers]
J[HttpOnly Cookies]
end
Teacher's Mindset
XSS is like a library where a vandal wrote a fake notice that says "click here to reset your password" on a real book page. The library should check all content before displaying it (sanitization) and display it in a protective sleeve (encoding).
Preventing XSS in APIs
from flask import Flask, request, jsonify
import bleach
import html
app = Flask(__name__)
ALLOWED_TAGS = ["b", "i", "em", "strong", "p", "br"]
ALLOWED_ATTRS = {}
@app.route("/api/posts", methods=["POST"])
def create_post():
data = request.json
raw_content = data.get("content", "")
safe_content = bleach.clean(
raw_content,
tags=ALLOWED_TAGS,
attributes=ALLOWED_ATTRS,
strip=True,
strip_comments=True
)
return jsonify({
"content": safe_content,
"sanitized": raw_content != safe_content
})
# Set security headers for XSS prevention
@app.after_request
def add_security_headers(response):
response.headers["X-XSS-Protection"] = "1; mode=block"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["Content-Security-Policy"] = (
"default-src 'self'; "
"script-src 'self'; "
"style-src 'self' 'unsafe-inline'"
)
return response
# Reflected XSS prevention
import urllib.parse
def validate_redirect_url(url):
parsed = urllib.parse.urlparse(url)
if parsed.netloc and parsed.netloc != "example.com":
raise ValueError("External redirect blocked")
return html.escape(url)
@app.route("/api/redirect")
def redirect():
target = request.args.get("url", "")
try:
safe_url = validate_redirect_url(target)
return jsonify({"redirect": safe_url})
except ValueError as e:
return jsonify({"error": str(e)}), 400
Common Mistakes
| Mistake | Why It's Wrong | Fix |
|---|---|---|
| Returning raw HTML from API | Every XSS payload executes in the browser | Encode all user-controlled output |
| Setting Content-Type to text/html | Browser interprets response as HTML | Always use application/json for APIs |
| Trusting CORS to prevent XSS | CORS does not prevent XSS, only cross-origin reads | CSP + output encoding are necessary |
| Not encoding JSON callback names | JSONP callback injection | Avoid JSONP, use CORS instead |
| Whitelisting |