security: harden JWT auth with session binding, CSP headers, and sessionStorage
- Reduce access_token_ttl from 900s to 300s (5 min) to shrink XSS exploit window - Add session_id claim to JWT tokens tied to browser session (X-Session-Id header) - Flask middleware validates session_id matches header on every request - CSP headers: default-src/script-src 'self', no unsafe-inline/eval, frame-ancestors none - X-Content-Type-Options: nosniff on all responses - Move refresh token from localStorage to sessionStorage (tab-scoped, cleared on close) - Timing-safe password verification (dummy Argon2id for unknown users) - WebSocket auth also validates session_id header - Add 5 session_id tests and 3 CSP header tests
This commit is contained in:
+19
-1
@@ -175,7 +175,8 @@ def _auth_middleware():
|
||||
return jsonify({"ok": False, "error": "unauthorized"}), 401
|
||||
|
||||
token_string = auth_header[7:] # strip "Bearer "
|
||||
payload = validate_token(token_string, token_type="access")
|
||||
session_header = request.headers.get("X-Session-Id")
|
||||
payload = validate_token(token_string, token_type="access", session_id=session_header)
|
||||
if payload is None:
|
||||
return jsonify({"ok": False, "error": "unauthorized"}), 401
|
||||
|
||||
@@ -232,6 +233,23 @@ def _log_request_finish(response):
|
||||
elapsed_ms,
|
||||
)
|
||||
|
||||
# Content Security Policy — prevent inline script execution and XSS
|
||||
if "Content-Security-Policy" not in response.headers:
|
||||
response.headers["Content-Security-Policy"] = (
|
||||
"default-src 'self'; "
|
||||
"script-src 'self'; "
|
||||
"style-src 'self' 'unsafe-inline'; "
|
||||
"img-src 'self' data:; "
|
||||
"font-src 'self'; "
|
||||
"connect-src 'self'; "
|
||||
"frame-ancestors 'none'; "
|
||||
"base-uri 'self'; "
|
||||
"form-action 'self'"
|
||||
)
|
||||
|
||||
# set X-Content-Type-Options to prevent MIME sniffing
|
||||
response.headers["X-Content-Type-Options"] = "nosniff"
|
||||
|
||||
# Set cache headers: short in dev, long with staleness tolerance in prod
|
||||
if response.content_type.startswith("text/html"):
|
||||
# index.html: always short cache so browser revalidates
|
||||
|
||||
Reference in New Issue
Block a user