Broken Authentication in Fastapi with Hmac Signatures
Broken Authentication in Fastapi with Hmac Signatures — how this specific combination creates or exposes the vulnerability
Broken authentication occurs when authentication mechanisms are implemented incorrectly, allowing attackers to compromise credentials, tokens, or session states. In Fastapi, using Hmac Signatures without strict validation is a common source of broken authentication. Hmac Signatures rely on a shared secret to generate and verify a cryptographic signature of the payload. If the server does not enforce constant-time comparison, fails to validate the signature for every authenticated request, or uses weak secrets, the protection collapses.
Attackers can exploit weak or missing signature verification to tamper with tokens or parameters. For example, if the Fastapi route does not reject requests with missing or malformed signatures, an attacker can modify an unsigned or unsigned-modified payload and gain unauthorized access. Additionally, using predictable secrets or short keys makes brute-force or offline signature recovery feasible. Insufficient handling of algorithm negotiation (e.g., accepting multiple algorithms without strict enforcement) can lead to algorithm downgrade attacks, where the attacker forces the server to use a weaker or none algorithm.
Another vector specific to Hmac Signatures in Fastapi is improper construction of the signed string. If the canonical representation of headers, body, and timestamp is not consistent between the client and server, verification becomes unreliable. Without replay protection (e.g., nonce or timestamp validation), captured requests can be replayed to elevate privileges or impersonate users. Insecure storage or logging of the Hmac secret within the Fastapi application also increases risk, as compromised server code can expose the key and enable widespread signature forgery.
These issues map to the OWASP API Top 10 category for Broken Authentication and can lead to unauthorized access, privilege escalation, and account takeover. Because the attack surface involves cryptographic operations, errors are subtle and can persist through code reviews if the threat model does not explicitly consider implementation details around Hmac handling.
Hmac Signatures-Specific Remediation in Fastapi — concrete code fixes
Remediation focuses on strict signature generation, canonicalization, and verification, combined with secure secret management and replay protection. Below are concrete, working examples for Fastapi that demonstrate a secure pattern.
Secure Hmac Signature Generation and Verification in Fastapi
Use a strong secret stored as an environment variable and enforce Hmac-SHA256. Always include a timestamp and nonce to prevent replay attacks, and use a consistent canonical format for the signed string.
import os
import time
import hmac
import hashlib
import base64
from fastapi import FastAPI, Request, HTTPException, Header, Depends
from pydantic import BaseModel
app = FastAPI()
SECRET = os.environ.get("HMAC_SECRET")
if not SECRET:
raise RuntimeError("HMAC_SECRET environment variable is required")
def sign_payload(payload: str, secret: str) -> str:
key = secret.encode("utf-8")
msg = payload.encode("utf-8")
signature = hmac.new(key, msg, hashlib.sha256).digest()
return base64.b64encode(signature).decode("utf-8")
def build_canonical_string(method: str, path: str, timestamp: str, nonce: str, body: str) -> str:
# Canonical format: method|path|timestamp|nonce|body
return "|".join([method.upper(), path, timestamp, nonce, body])
class SignedRequest(BaseModel):
data: dict
timestamp: str
nonce: str
signature: str
async def verify_hmac(request: Request) -> None:
timestamp = request.headers.get("X-Request-Timestamp")
nonce = request.headers.get("X-Request-Nonce")
signature_header = request.headers.get("X-Request-Signature")
if not all([timestamp, nonce, signature_header]):
raise HTTPException(status_code=400, detail="Missing signature headers")
# Basic replay window: 5 minutes
now = str(int(time.time()))
ts_int = int(timestamp)
if abs(ts_int - int(now)) > 300:
raise HTTPException(status_code=401, detail="Request timestamp out of window")
body = await request.body()
body_str = body.decode("utf-8")
canonical = build_canonical_string(request.method, request.url.path, timestamp, nonce, body_str)
expected = sign_payload(canonical, SECRET)
if not hmac.compare_digest(expected, signature_header):
raise HTTPException(status_code=401, detail="Invalid signature")
@app.post("/secure-endpoint")
async def secure_endpoint(signed: SignedRequest, req: Request, _=Depends(verify_hmac)):
# Process verified request
return {"status": "ok", "received": signed.data}
This pattern ensures that each request includes a timestamp and nonce, uses constant-time comparison via hmac.compare_digest, and rejects requests outside an acceptable time window. The canonical string construction is explicit and deterministic, reducing risk of mismatched verification. For production, rotate the Hmac secret periodically and consider additional protections such as idempotency keys to further limit replay impact.
Client-side signing example
Clients must construct the canonical string identically and include the signature in headers.
import time
import hmac
import hashlib
import base64
import requests
def client_sign(method: str, path: str, secret: str, data: dict) -> dict:
import json
body = json.dumps(data, separators=(",", ":"))
timestamp = str(int(time.time()))
nonce = "unique-nonce-12345" # use a strong random in practice
canonical = "|".join([method.upper(), path, timestamp, nonce, body])
signature = hmac.new(secret.encode("utf-8"), canonical.encode("utf-8"), hashlib.sha256).digest()
return {
"method": method,
"path": path,
"timestamp": timestamp,
"nonce": nonce,
"body": body,
"headers": {
"X-Request-Timestamp": timestamp,
"X-Request-Nonce": nonce,
"X-Request-Signature": base64.b64encode(signature).decode("utf-8"),
},
"body": body,
}
# Example usage
secret = os.environ.get("CLIENT_HMAC_SECRET")
payload = client_sign("POST", "/secure-endpoint", secret, {"action": "update", "id": 42})
resp = requests.post("https://api.example.com/secure-endpoint", json=payload["body"], headers=payload["headers"])
Related CWEs: authentication
| CWE ID | Name | Severity |
|---|---|---|
| CWE-287 | Improper Authentication | CRITICAL |
| CWE-306 | Missing Authentication for Critical Function | CRITICAL |
| CWE-307 | Brute Force | HIGH |
| CWE-308 | Single-Factor Authentication | MEDIUM |
| CWE-309 | Use of Password System for Primary Authentication | MEDIUM |
| CWE-347 | Improper Verification of Cryptographic Signature | HIGH |
| CWE-384 | Session Fixation | HIGH |
| CWE-521 | Weak Password Requirements | MEDIUM |
| CWE-613 | Insufficient Session Expiration | MEDIUM |
| CWE-640 | Weak Password Recovery | HIGH |