Xss Cross Site Scripting in Fastapi with Jwt Tokens
Xss Cross Site Scripting in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Cross-site scripting (XSS) in a FastAPI application that uses JWT tokens typically arises when an API returns data that eventually reaches a browser context without adequate escaping. Even when endpoints are protected by JWT-based authentication, improper handling of user-controlled data can introduce injection points. For example, an endpoint might embed user-controlled values into JSON responses that a single-page application consumes and then render via innerHTML or unsafe template interpolation. In such cases, the presence of a valid JWT does not mitigate XSS; it only confirms the request’s authenticity, while malicious payloads execute in the victim’s browser with the victim’s permissions.
Another common pattern is token leakage in URLs or logs. If a FastAPI route includes the JWT in query parameters (e.g., for sharing or tracking), and the response reflects the token or related data without sanitization, an attacker can craft a link that triggers XSS when the victim visits it. Compounded by misconfigured CORS or overly permissive origins, this can allow injected scripts to make authenticated requests on behalf of the user. While the JWT validates identity, it does not validate or sanitize content, so any reflected or stored data must be escaped for the target context.
Consider a FastAPI endpoint that returns user-controlled fields within a JSON structure consumed by JavaScript:
from fastapi import FastAPI
from pydantic import BaseModel
app = FastAPI()
class Item(BaseModel):
name: str
description: str
@app.get("/items/{item_id}")
def read_item(item_id: int, user_token: str = None):
# user_token is extracted from Authorization header
return {"item_id": item_id, "name": "" + item_id + "", "description": "Hello"}
If the name field originates from user input and the response is rendered in a vulnerable frontend, an attacker can supply name as <script>stealCookies()</script>. Even with JWT authentication present, the reflected script executes in the browser. This illustrates why JWT tokens must be treated strictly as credentials, while output encoding and strict content-type headers remain essential for XSS prevention.
Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes
Remediation focuses on strict separation of authentication and output safety. JWT tokens should only be used to establish identity, never to decide how data is encoded for different contexts. Always enforce strong Content-Security-Policy headers, validate and sanitize all user-supplied data, and use secure default headers.
In FastAPI, configure security headers and response encoding explicitly. For example, set headers to prevent MIME-sniffing and enforce JSON content types to reduce XSS surface:
from fastapi import FastAPI, Request
from fastapi.middleware.cors import CORSMiddleware
from starlette.middleware.base import BaseHTTPMiddleware
from starlette.responses import JSONResponse
import json
app = FastAPI()
# Restrict origins instead of using wildcard
app.add_middleware(
CORSMiddleware,
allow_origins=["https://trusted.example.com"],
allow_credentials=True,
allow_methods=["GET"],
allow_headers=["Authorization", "Content-Type"],
)
class SecurityHeadersMiddleware(BaseHTTPMiddleware):
async def dispatch(self, request, call_next):
response = await call_next(request)
response.headers["Content-Security-Policy"] = "default-src 'self'; script-src 'self' https://trusted-cdn.example.com; object-src 'none'"
response.headers["X-Content-Type-Options"] = "nosniff"
response.headers["X-Frame-Options"] = "DENY"
return response
app.add_middleware(SecurityHeadersMiddleware)
When returning user data, ensure proper escaping for the intended context. For JSON responses, rely on strict schemas and avoid embedding HTML. If HTML must be rendered, encode on the client using a well-maintained library and never use innerHTML. Here’s a safer endpoint example that validates input and avoids reflection of sensitive tokens:
from fastapi import FastAPI, Depends, HTTPException, status
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from pydantic import BaseModel, validator
import re
app = FastAPI()
security_scheme = HTTPBearer()
class ItemResponse(BaseModel):
item_id: int
name: str
description: str
@validator("name")
def validate_name(cls, v):
# Basic example: allow only alphanumeric, spaces, and a few safe chars
if not re.match(r"^[A-Za-z0-9 _\-]*$", v):
raise ValueError("Name contains invalid characters")
return v
def get_current_token(credentials: HTTPAuthorizationCredentials = Depends(security_scheme)):
# Perform JWT validation and return payload or raise
if not credentials.credentials or not credentials.credentials.startswith("Bearer "):
raise HTTPException(status_code=status.HTTP_401_UNAUTHORIZED, detail="Invalid authentication")
token = credentials.credentials.split(" ")[1]
# Decode and verify token using your preferred library
return token
@app.get("/items/{item_id}", response_model=ItemResponse)
def read_item(
item_id: int,
token: str = Depends(get_current_token),
):
# Use validated item_id and safe name construction
name = f"Item-{item_id}"
return ItemResponse(item_id=item_id, name=name, description="Hello")
These patterns ensure JWT tokens are handled as credentials only, while output contexts are secured through validation, safe defaults, and context-aware encoding.
Related CWEs: inputValidation
| CWE ID | Name | Severity |
|---|---|---|
| CWE-20 | Improper Input Validation | HIGH |
| CWE-22 | Path Traversal | HIGH |
| CWE-74 | Injection | CRITICAL |
| CWE-77 | Command Injection | CRITICAL |
| CWE-78 | OS Command Injection | CRITICAL |
| CWE-79 | Cross-site Scripting (XSS) | HIGH |
| CWE-89 | SQL Injection | CRITICAL |
| CWE-90 | LDAP Injection | HIGH |
| CWE-91 | XML Injection | HIGH |
| CWE-94 | Code Injection | CRITICAL |