4 ms·
Hi, Mercure author here. WebSockets are hard to secure (they totally bypass CORS as well as other browser built-in protections), don't work (yet) with HTTP/3 a
by kdunglas 2y ago
Hi, Mercure author here.
WebSockets are hard to secure (they totally bypass CORS as well as other browser built-in protections), don't work (yet) with HTTP/3 and for most use cases require to implement many features by yourself: reconnection in case of network failure, refetch of lost messages, authorization, topic mechanism…
Mercure, which is built on top of SSE (it's more an extension to SSE than an alternative to it) fix these issues.
However, SSE (as well as WebSockets) can be hard to use with stacks not designed to handle persistent connections such as PHP, serverless, most web servers proxying Ruby, Python etc apps. Even for languages designed to handle persistent connections, it's often more efficient and easier to manage persistent connections with ah-hoc software running on dedicated hardware.
That's what Mercure allows. Mercure provides a "hub", a server that will maintain the persistent connections with the browsers, store events, and re-send them in case of network issues (or if asked for old messages by a client). To broadcast a message to all connected users, the server app (or even a client) can just send a single POST request to the hub. The hub will also check that clients are authorized to subscribe or publish to a given topic (that's why JWT is used).
The reference implementation is written in Go, as a module for the Caddy web server, and his very efficient/optimized (it can handle thousands of persistent connections on very small machines).
Install a Mercure hub and you have all these features available without having to write any code. Client-side, no SDK is required, you can embrace the built-in EventSource JavaScript class.
- Thorrez 2y ago> they totally bypass CORS You can reimplement the Same Origin Policy serverside by checking that the Origin header equals the Host header. Even more secure would be to check both against an allowlist (this protects against DNS rebinding, which the Same Origin Policy doesn't protect against). >as well as other browser built-in protections I'm curious what those are. >for most use cases require to implement many features by yourself: [...] authorization Isn't auth of websockets generally the same as auth of any Javascript-initiated HTTP request (e.g. fetch())? Check that the cookie looks good? Now, in the cause of OAuth tokens, websockets are more difficult than fetch(), because you cannot attach an Authorization: Bearer header to a websocket. But OAuth is less common than cookies for websites.
- kdunglas 2y agoOnce the connection is upgraded, you loose all metadata included in the HTTP headers (because it’s not HTTP) and all protections relying on it. Also CORS and SOP can be bypassed: https://dev.to/pssingh21/websockets-bypassing-sop-cors-5ajm https://dev.to/pssingh21/websockets-bypassing-sop-cors-5ajm Even very experienced teams make mistake with this kind of things. See for instance, this Kubernetes vulnerability: https://goteleport.com/blog/kubernetes-websocket-upgrade-security-vulnerability/ https://goteleport.com/blog/kubernetes-websocket-upgrade-sec... Of course you can reimplement everything by hand (and you must if you use WebSockets), but with SSE/Mercure you don't have to because it's plain old HTTP.
- whilenot-dev 2y ago> Once the connection is upgraded, you loose all metadata included in the HTTP headers (because it’s not HTTP) and all protections relying on it. The Upgrade request is HTTP and you can extract all needed metadata from there and store it server side as needed. Those metadata wouldn't change during an active WebSocket session anyway, would they?
- matharmin 2y agoWith your own native client: Yes, you can send arbitrary headers in the Upgrade request. In a browser however, you can't. It typically sets very little headers itself, and doesn't allow you to add custom headers.
- whilenot-dev 2y agoThe auth headers (Authorization, Cookie) are all passed along, and that's what I want to establish a secure connection from the browser. For more customized wishes there's always this "ticket"-based flow[0][1] that shouldn't be hard to implement. I might be a bit naive, but what needed metadata and custom headers are we talking about? [0]: https://devcenter.heroku.com/articles/websocket-security#authentication-authorization https://devcenter.heroku.com/articles/websocket-security#aut... [1]: https://lucumr.pocoo.org/2012/9/24/websockets-101/#authorization-and-ips https://lucumr.pocoo.org/2012/9/24/websockets-101/#authoriza...
- whilenot-dev 2y agoIt might be worth mentioning to the parent poster that SSEs (and Mercure) are unidirectional only (server -> client), whereas WebSockets are bidirectional.
- the_mitsuhiko 2y agoNowadays that's hardly an issue because SSE is multiplexed over HTTP/2 and HTTP/3 alongside regular requests. In some ways, SSE is today more efficient than websockets too because if you use it with HTTP/3 then you're avoiding head of line blocking which is still an issue with websockets. (Also in practice many places websockets are not even supporting HTTP/2 because on the server they were implemented on top of raw sockets which is no longer possible with HTTP/2)
- chris_pie 2y agoHead-of-line still exists in HTTP/3, but on another level. A stream can still get blocked. With websockets the whole connection gets blocked (HTTP/1), but that connection isn't shared with anything else, right? Worth noting that HTTP/3 seems to recover faster than TCP-based protocols.
- archerx 2y agoI have been using Server Sent Events with PHP since 2017 and it works very well. All you need is the right headers and a loop that breaks on user disconnect. Honestly it was super easy and had no issues setting it up and getting it to work.
- spintin 2y ago[dead]
- panarky 2y agoThis is helpful, thank you. You might consider updating the readme to make it clear what Mercure/SSE does that WebSockets doesn't. If it was me, I'd also put in the readme what WebSockets does that Mercure/SSE does not, such as bidirectional communication, low latency client-to-server messages, binary data transfer, etc.
- gregors 2y agoExactly this, the landing page seem much more about the reference implementation than the actual protocol. I will say the docs at https://mercure.rocks/spec https://mercure.rocks/spec are nice.
- coolhand2120 2y ago> WebSockets are hard to secure (they totally bypass CORS as well as other browser built-in protections), don't work (yet) with HTTP/3 and for most use cases require to implement many features by yourself: reconnection in case of network failure, refetch of lost messages, authorization, topic mechanism… Having written WebSocket CORS with Authentication (Cognito) I know this isn't quite true. The initial connection is a standard HTTP request that returns a 100 series, with preflights and everything. That initial request has all the headers you might want to sent to a server for auth. It's a bit odd IMO but the auth string is sent in the web socket constructor, second argument, really easy to miss. Happy to provide both server and client code examples. https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/WebSocket#protocols https://developer.mozilla.org/en-US/docs/Web/API/WebSocket/W...