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:
@@ -195,6 +195,31 @@ def auth_refresh(_request: Any, body: Any) -> dict[str, Any]:
|
||||
},
|
||||
"permissions": permissions,
|
||||
}
|
||||
if payload is None:
|
||||
raise ValueError("Invalid or expired refresh token")
|
||||
|
||||
username = payload["sub"]
|
||||
user = get_user(username)
|
||||
if user is None:
|
||||
raise ValueError("User not found")
|
||||
|
||||
jti = payload.get("jti")
|
||||
if jti:
|
||||
blacklist_token(jti, token_type="refresh")
|
||||
if username:
|
||||
_clear_refresh_token_after_rotation(username)
|
||||
permissions = user["permissions"]
|
||||
tokens = generate_tokens(username, permissions)
|
||||
|
||||
return {
|
||||
"tokens": tokens,
|
||||
"access_ttl": get_access_ttl(),
|
||||
"user": {
|
||||
"id": user["id"],
|
||||
"username": user["username"],
|
||||
},
|
||||
"permissions": permissions,
|
||||
}
|
||||
|
||||
|
||||
@registry.register(GET_AUTH_SESSION)
|
||||
|
||||
+2
-1
@@ -396,7 +396,8 @@ async def _handle_ws(request: web.Request) -> web.Response:
|
||||
{"ok": False, "error": "authentication required"}, status=401
|
||||
)
|
||||
|
||||
payload = validate_token(token_param, token_type="access")
|
||||
session_header = request.headers.get("X-Session-Id")
|
||||
payload = validate_token(token_param, token_type="access", session_id=session_header)
|
||||
if payload is None:
|
||||
return web.json_response({"ok": False, "error": "unauthorized"}, status=401)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user