Injection Flaws in Fastapi with Jwt Tokens
Injection Flaws in Fastapi with Jwt Tokens — how this specific combination creates or exposes the vulnerability
Injection flaws in FastAPI applications that rely on JWT tokens often arise when developer focus on token validation obscures other input-handling gaps. A JWT typically arrives in the Authorization header as a bearer token; if the API decodes and trusts the payload without additional checks, it may treat decoded claims as safe for downstream use. For example, a claim such as sub or custom fields may be interpolated into dynamic queries, command construction, or serialization logic without adequate validation, enabling injection against databases, object–relational mappers, or system commands. This is especially risky when token-based authentication is implemented using libraries that do not strictly separate authentication from authorization and input validation.
Consider a FastAPI endpoint that decodes a JWT and uses a user identifier from the token to build a dynamic query without parameterization:
import jwt
from fastapi import Depends, FastAPI, HTTPException
from pydantic import BaseModel
SECRET = "super-secret-key"
app = FastAPI()
def get_token_auth_scheme(token: str = Depends(OAuth2PasswordBearer(tokenUrl="login"))):
try:
payload = jwt.decode(token, SECRET, algorithms=["HS256"])
return payload
except jwt.PyJWTError:
raise HTTPException(status_code=401, detail="Invalid token")
@app.get("/items")
async def read_items(token_payload: dict = Depends(get_token_auth_scheme)):
user_id = token_payload.get("sub")
# Unsafe string formatting: vulnerable to injection if user_id is used later
query = f"SELECT * FROM items WHERE owner_id = {user_id}"
# execute_query is hypothetical; illustrates unsafe usage
results = execute_query(query)
return {"items": results}
In this pattern, user_id from the JWT is inserted directly into a string, which can lead to SQL injection if the token is compromised or the claim is manipulated in other parts of the request lifecycle. Even if the token itself is cryptographically valid, an attacker who can influence runtime data (e.g., path or query parameters) may still exploit weak composition of authorization and input validation. For instance, an endpoint that accepts an item_id query parameter and uses it in a raw string alongside the JWT-derived user_id increases the attack surface.
Another scenario involves command injection when decoded JWT claims are passed to system utilities or external APIs without strict allowlisting. A FastAPI route that uses a JWT claim to construct a shell command—such as a username used in a filename—can be abused if the input is not validated against a whitelist of permitted characters and lengths. Similarly, deserialization or dynamic model construction based on JWT metadata can introduce injection risks if the application does not rigorously validate types, lengths, and formats of all incoming data, regardless of token validity.
SSRF and external request injection can also be exacerbated when JWT claims influence target URLs or headers without validation. For example, if a redirect_uri claim is used to construct outbound requests, an attacker may supply a malicious host if the application does not enforce strict allowlists and canonicalization. Therefore, treating the JWT payload as a source of trust rather than a source of identity requires disciplined input validation, strict type checking, and separation of authentication from data processing.
Jwt Tokens-Specific Remediation in Fastapi — concrete code fixes
Remediation centers on strict validation, parameterization, and avoiding string interpolation for queries or commands. Always treat data from JWT payloads as untrusted for data access or system interactions. Use prepared statements or an ORM with parameterized queries, and validate each claim against an allowlist before use.
Secure example using SQL parameterization with a library such as asyncpg or SQLAlchemy:
import jwt
from fastapi import Depends, FastAPI, HTTPException
from pydantic import BaseModel
SECRET = "super-secret-key"
app = FastAPI()
def get_token_auth_scheme(token: str = Depends(OAuth2PasswordBearer(tokenUrl="login"))):
try:
payload = jwt.decode(token, SECRET, algorithms=["HS256"])
return payload
except jwt.PyJWTError:
raise HTTPException(status_code=401, detail="Invalid token")
@app.get("/items")
async def read_items(token_payload: dict = Depends(get_token_auth_scheme), db_pool=Depends(get_db_pool)):
user_id = token_payload.get("sub")
if not isinstance(user_id, int) or user_id <= 0:
raise HTTPException(status_code=400, detail="Invalid user identifier")
async with db_pool.acquire() as conn:
# Parameterized query prevents SQL injection
rows = await conn.fetch("SELECT * FROM items WHERE owner_id = $1", user_id)
return {"items": [dict(r) for r in rows]}
For command construction, avoid shell interpolation entirely. If a system command is necessary, use a strict allowlist and structured invocation:
import shlex
import subprocess
from fastapi import Depends
def safe_process_user_input(token_payload: dict = Depends(get_token_auth_scheme)):
username = token_payload.get("preferred_username", "")
# Strict allowlist: only alphanumeric and underscore
if not re.match(r'^[A-Za-z0-9_]+$', username):
raise HTTPException(status_code=400, detail="Invalid username format")
# Use list arguments to avoid shell injection
result = subprocess.run(["/usr/bin/procname", "--user", username], capture_output=True, text=True)
return {"stdout": result.stdout}
Validate and normalize all claims used for routing, object ownership, or external requests. Enforce type checks and length limits, and prefer structured data over string concatenation. Combine these practices with FastAPI dependency injection to centralize validation logic, ensuring that JWT-based identity does not bypass input hygiene.