-
-
Notifications
You must be signed in to change notification settings - Fork 5
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
WebSocket fails via CSRFMiddleware #14
Comments
Indeed, I've never considered the case of WebSocket, so it's probable the code is not adequate for such requests. I'm not sure right now how this can be solved, but I'll investigate. Thank you for the report 🙏 |
@johnpaulett Not sure if this'll work for ya but I "fixed" this by only applying CSRF to class CustomCSRFMiddleware(CSRFMiddleware):
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] != "http":
await self.app(scope, receive, send)
return
await super().__call__(scope, receive, send) |
Sharing my workaround FWIW. Because of whatwg/websockets#16 I am putting the CSRF token as a separate cookie instead of a header. I don't think it would be considered as secure but it's better than nothing. from starlette.websockets import WebSocket
# Most of this logic is copied from the existing implementation.
class CSRFMiddleware(StarletteCSRFMiddleware):
async def __call__(self, scope: Scope, receive: Receive, send: Send) -> None:
if scope["type"] == "websocket":
request = WebSocket(scope, receive, send)
csrf_cookie = request.cookies.get(self.cookie_name)
if self._url_is_required(request.url) or (
not self._url_is_exempt(request.url)
and self._has_sensitive_cookies(request.cookies)
):
submitted_csrf_token = request.cookies.get("header_x_csrf_token")
if (
not csrf_cookie
or not submitted_csrf_token
or not self._csrf_tokens_match(csrf_cookie, submitted_csrf_token)
):
response = self._get_error_response(request) # type: ignore
await response(scope, receive, send)
return
send = functools.partial(self.send, send=send, scope=scope)
await self.app(scope, receive, send)
else:
await super().__call__(scope, receive, send)
async def send(self, message: Message, send: Send, scope: Scope) -> None:
request = (
WebSocket(scope, None, send) # type: ignore
if scope["type"] == "websocket"
else Request(scope)
)
csrf_cookie = request.cookies.get(self.cookie_name)
if csrf_cookie is None:
message.setdefault("headers", [])
headers = MutableHeaders(scope=message)
cookie: http.cookies.BaseCookie = http.cookies.SimpleCookie()
cookie_name = self.cookie_name
cookie[cookie_name] = self._generate_csrf_token()
cookie[cookie_name]["path"] = self.cookie_path
cookie[cookie_name]["secure"] = self.cookie_secure
cookie[cookie_name]["httponly"] = self.cookie_httponly
cookie[cookie_name]["samesite"] = self.cookie_samesite
if self.cookie_domain is not None:
cookie[cookie_name]["domain"] = self.cookie_domain # pragma: no cover
headers.append("set-cookie", cookie.output(header="").strip())
await send(message) |
Ideally, I'd like to use the CSRFMiddleware on a websocket route. But at present, the CSRFMiddleware makes hits an assertion error whenever the websocket route is accessed:
Error
Attempted solutions
exempt_urls
-- think the error happens before this is every appliedrequired_urls
-- I hoped that maybe the initial HTTP connection that gets upgrade would pass thrurequest = Request(scope)
withrequest = WebSocket(scope, receive=receive, send=send) if scope["type"] == "websocket" else Request(scope)
, but still occurredCurrent workaround
I wrap CSRFMiddleware and only pass HTTP requests into it. This is suboptimal because I would like to enforce CSRF protection for my websocket route.
Partial test case
I tried to document the error in a testcase, but using the httpx client does not expose the
.websocket_connect()
that starlette's testclient exposes. So this code does not yet fully workHappy to try to help with some pointers.
Upvote & Fund
The text was updated successfully, but these errors were encountered: