I have a platform where multiple restaurants (let's say 100) can receive online orders from users. The admin of the restaurant has a live panel where all the incoming orders are displayed. So when a new order is placed the admin must receive a push notification without reloading the page.
To accomplish this I have used Websockets. I have implemented this Websocket Chat Example.
So there are different chat rooms in which I can push a message from the server. I was thinking about the following way to implement the push notifications:
When a restaurant admin opens his live order panel, a unique secret key gets generated server-side and saved in the client-side session of the admin.
That secret key is the name of the chat-room.
Each time an order gets placed a string with the ID of the order is sent through the websocket from the server to the admin's order-panel.
AJAX retrieves the order details by using the ID received by the websocket.
Yeah but I have already implemented the Websockets... So I am asking if my solution is okay as well. Are there safety or performance concerns by using websocket in the way I did?
Seems fine but a bit over-engineered maybe? The WebSocket is already within the context of the admin's login session, so what's the extra secret key gaining you?
I currently don't have a unique identifier for a restaurant. Only a primary key ID (auto_increment) which of course is easy to guess.
Would you suggest adding a column secret_identifier to the database table containing the restaurants? Wouldn't a newly generated key for each session be more safe then?
No, I'd suggest that you keep access to the websocket order stream for each restaurant secure not with a secret, but through user authentication.
I don't know how you manage authentication and authorization in your app, but lets assume you are using access tokens (i.e. OpenID Connect) for now (if you use sessions or some other mechanism, I'm sure this example is translatable).
Say your admin logs into your application and navigates to the orders page. The order page establishes a connection to the websocket stream by calling GET /restaurant/{restaurant 1}/orders. Now in order to make sure the authenticated user can access the websocket stream for restaurant 1, I'd send the access token (or session id, or other form of credentials) as part of the HTTP request's header, for example the authorization header as bearer token: Authorization: Bearer $TOKEN. The endpoint validates the credentials and makes sure the user has access based on the rules you define (i.e. user belongs to group restaurant 1 and has admin rights in that group). If that operation is successful, you return the successfully established websocket connection. If not, return a error 401 Unauthorized.
That looks like you check user authorization and only after that pans out you connect your websocket, which is what I had in mind. Where is that secret key coming into place? Or have you removed it already from the example?
All restaurants have the same Javascript code of course. So all get subscribed to room 'main'. This means all orders will get pushed to the chatroom 'main' and thus all restaurants will receive ALL the orders and not only the orders unique to them.
Exactly. If the user doesn't have access to that room, he can't connect to that room.
You can probably use the session object with the user information for connecting to the right room (if your users can only ever belong to one restaurant) or send something as part of the connection request.
I mean I don't understand the messages you send on that channel when you connect, especially /join my_restaurant_name looks like it isn't doing anything, because we already established which room the client belongs to in this call:
but other than that, this is pretty much how I'd do it.