-
Notifications
You must be signed in to change notification settings - Fork 60
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
GraphQL over WebSocket #140
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for doing this work @enisdenjo 🙌 I've not read this in sufficient detail to be sure there's not any major issues, but I did spot one thing about ConnectionInit you might want to address.
Just my two cents but I think we should drop Something like this would be more optimal since the connection is rejected right before the upgrade (in this spec, the socket is established either way). We should steer user over that direction instead. For There is no mention of The less the better, imo. |
A protocol should be about security too. It can help steer decisions with a standardised backbone of staying protected. But, in the same time it should be flexible enough to allow bullet-proofing your system with a custom implementation. Keeping this in mind, the protocol does not specify how you should authenticate, whether you should use cookies or tokens to uniquely identify clients; it simply specifies the means for communicating authorisation and a concrete way to prevent unwanted access. The most secure and reliable way to authenticate through HTTP is by using headers. Unfortunately, you cannot pass custom headers in the WebSocket HTTP upgrade request from the browser - so this auth approach is simply not feasible. Well, there are still cookies which the browser handles and provides through headers regardless of the HTTP request. However, this approach is prone to Cross-Site WebSocket Hijacking (CSWSH). There are measures you can take to protect yourself (which are applicable in general), yet the conclusion is still:
Furthermore, URLs should never be used for transferring secrets. Back in 2012, the OAuth specification provided recommendations on threat mitigations. One of the items in the summary is:
|
I don't think that "live queries" should be specified in this spec - there's not an established approach for live queries, they can work in multiple ways depending on the vendor (sometimes as If you're using |
My intention with this commit was just to elevate the restriction of single emission on But, you're right! I am misusing the Thanks @benjie! |
In my opinion, this should not be part of the spec. It looks more like extra implementations by specific libraries. For example current GraphQL HTTP servers/libraries often have things like Likewise |
It is true that there are security implications in using query params but this approach is still used in many production products. For example, Spotify creates a WebSocket connection by including It is always possible to make the access token short-lived so counter security implications. We provide Since the current HTTP spec do not specify how we should handle authentication or gate-keeping, this spec should not either. Specific GraphQL library can still implemented such feature as an extra. My point is that this spec should be inopinionated as possible (like the current HTTP spec which does not include such things). |
Great feedback! I liked a lot of your suggestions and added them in the recent commit. I also somewhat elevated authentication restrictions from the protocol, you should be free to deny the WebSocket upgrade through HTTP however you like before even reaching to the point of communication. However, I left the connection Still, I think much of your points make sense, would like to hear more opinions on this topic. |
Just wrote some interoperability suggestions, maybe someone wants to jump into the discussion: enisdenjo/graphql-ws#83 |
@enisdenjo Sorry about the delay, hope this answers everything. GraphQL Over WebRTC
Sounds good. I'm not immediately sure how to phrase "All the message types from GraphQL over WebSocket plus a connectionError message" for the spec but that sounds like something that can be iterated in a separate PR. For reference: A proof of concept GraphQL over WebRTC can be found here. Live Queries
So before I get into detail I'm not 100% sure whether this belongs in GraphQL Over WebSocket or the GraphQL spec itself. Perhaps that's something you can provide insight on! The patch field allows a An example workflow would go like this:
{
id: '1',
type: 'next',
payload: {
data: {
article: {
title: 'Cat Facts',
likes: 1,
likedBy: [
"bill-the-cat-dude@example.com"
],
markdownFullText: "# Imagine a few pages of markdown here"
}
}
},
}
{
id: '1',
type: 'next',
payload: {
patch: [
{
"op": "replace",
"path": "/article/likes",
"value": 2,
},
{
"op": "add",
"path": "/article/likedBy/1",
"value": "bills-friend-who-also-likes-cats@example.com",
}
]
},
} An implementation of the patch response payload can be found here |
@D1plo1d sounds cool! However, I am wary of changing the Protocol to support additional real-time transports because as we broaden the transport spectrum, we deviate from the WebSocket's transport itself - leaving much of its intricacies to guessing (or misuse). Your change requirement is a perfect example of this, as soon as we allow usage of Furthermore, seem like in the WebRTC 1.0 spec you can announce a data channel as closed with an error. Could this potentially be synonymous to the WebSockets close event?
@D1plo1d I'd say this is more of an issue with the GraphQL spec because you're actually changing the |
@enisdenjo Sounds good on both fronts. Re: Announcing close WebRTC data channels: I'm reading through this now but I think this is specifying if the underly SCTP transport closes with an error as opposed to an error that is settable from within JavaScript which would be more like websockets. In any case a seperate GraphQL Over WebRTC proposal can follow and build off of this one. |
About the client to server ping topic: I hope enisdenjo/graphql-ws#117 (comment) shines some light on how all argumented problems can be solved without any Protocol adjustments. |
Heads up everybody, this Protocol now supports bidirectional subprotocol ping and pong message types. More info on the PR: enisdenjo/graphql-ws#201, together with the full discussion: enisdenjo/graphql-ws#117. 😄 |
Why do we choose to include this in the spec? I think it might be better to be a library-specific implementation. Since I think |
Also because it is a transport concern. Please read more here: enisdenjo/graphql-ws#117. |
To summarise the ping/pong message addition:
|
Thanks for such a detailed answer! I understood and it made sense. However notice that a libarary can always handle ping/pong outside of the transport by directly using the underlying WebSockets API. For example: function someGraphqlWsLibary(ws, options) {
ws.on('connection', (socket, request) => {
handleGraphQL(socket, request)
})
// library will add another listener
if (options.usePingPong) {
ws.on('connection', (socket, request) => {
handlePingPongThing(socket)
})
}
} This spec should be more about integration of GraphQL on WebSocket. And GraphQL is more about execution/validation of a request rather than connectivity status of a specific transport. Notice that this GraphQL spec is a protocol of a transport, not the entire transport itself: we can more than just GraphQL living on the WebSocket (like some regular WebSocket APIs, some socketio channel, etc.). Delegating the connection stayalive to GraphQL WS handler, (sometimes) a component of the entire WebSocket setup, may not be the best idea. But I think the use case for adding ping-pong does have their sense too, especially dx. Let's see what others think about this. |
I understand your argument; however, it is not possible to handle ping pongs in the same WebSocket using the What you're aiming at would require a separate connection, which is an even greater overkill than polluting your GraphQL schema with ping/pong fields (as suggested here: enisdenjo/graphql-ws#117 (comment)). If using the WebSocket spec level pings and pongs would be an option (by having a consistent implementation across all parties and services), this protocol would've never mentioned them (which was my original path); however, the plain reality is that you cannot use them consistently. Hence, the need of ping/pong message types. Furthermore, the protocol document simply allows them, and vaguely describes their intent; but, you're absolutely free not to use them at all, there is no requirement. This is exactly the default mode of the |
It's really exciting to see this protocol standardization coming together! Out of curiosity, is there anything written anywhere that compares this protocol to the old subscriptions-transport-ws protocol? While I trust that you're correct in believing that this protocol improves security and stability and reduces ambiguity, I'd be curious to understand more precisely how it does that. (Do I understand correctly that the (For context, I'm the lead maintainer of Apollo Server. We originally built |
Hey there, I am very excited too! I'll jump straight to the point. This protocol is designed to accompany WebSockets and implement on top of it. One example of it is that it does not have a "GQL_CONNECTION_TERMINATE" message and it rather uses the WS level close and communicates the reasoning through the close event's code and reason. It removes unnecessary messages and reduces the type name sizes increasing efficiency by a few notches. It was also originally designed without describing a keep-alive mechanism (allowing the implementor to leverage the WebSocket spec level "heartbeat"), but because of #140 (comment), subprotocol level ping and pongs were introduced. These pings/pongs are much more useful and extensible than unidirectional keep-alive message (comparing Furthermore, security-wise, it is very strict on the authentication process and requires message conformity otherwise kicking the client off immediately. For example, it does not allow invalid messages (messages not defined by the protocol) and it absolutely requires a single connection acknowledgement message which also has a timeout. Read more about the reasoning behind the acknowledgement message here: #140 (comment) and here: #140 (comment). Also, is a bit more descriptive about what each message transmits and how that looks like. Exactly describing the JSON fields accompanying messages, allowing it to classify everything non-compliant as invalid. In essence, it is inspired by the initial
Yes, two reasons: I hope I didnt overlook anything and managed to answer your questions. 😄 |
@enisdenjo Thanks, that's great! We're not quite at the point of kicking off the "subscriptions at Apollo, for real this time" project but when we are I'd definitely love to chat more. |
@glasser cool, thats fine! I'd just like to point out that you dont need to rip the bandage off suddenly by kicking off subscriptions completely. You can support both in parallel (server and client side) to ease off the transition because of the distinct subprotocol names. For the server, you can consult the "ws server usage with subscriptions-transport-ws backwards compatibility" recipe. And for the client, you simply can do a test-try connect to a server and check if the socket got closed with a 1002. Very similar to the GraphQL Playground P.S. left a comment at the AS3 issue (apollographql/apollo-server#4960 (comment)) too. Better to discuss the specifics of supporting both there instead. |
My 2 cents, SSE are unidirectional, and if there are tweaks for the Ping/Pong part of the protocol, the same can be used for Graphql over SSE. This is somehting I'm working on for Android / kotlin clients, pls see apollographql/apollo-kotlin#3756 |
As discussed in enisdenjo/graphql-ws#340
As this is an RFC, it should be merged. (I cannot merge it currently.) |
Read the formatted GraphQL over WebSocket Protocol
Implementations