Sails.js authorization for socket requests - session

I'm trying to build a chat application based on sails.js. The url for messages from a specific chat looks like this:
/api/chat/:id/messages
When I request this url with XHR, it provides a session cookie and sails.js builds a session object. I can easily check user rights to read the messages from the specific chat.
However, I need to request this url with socket.io so that the client can subscribe to all future changes of the messages collection.
When I request this url with socket.io, no session cookie is set and the sails.js session is empty. So, I cannot check the user rights on the server-side.
I do understand that socket requests are not HTTP-requests. They don't provide any cookies on their own.
Is there any simple workaround?

I found a way to get the session object which was set while socket.io handshaking.
In your controller, you should do something like this:
myControllerAction: function(req, res) {
var session = req.session;
if (req.isSocket) {
var handshake = req.socket.manager.handshaken[req.socket.id];
if (handshake) {
session = handshake.session;
}
}
//session now contains proper session object
}
You can implement this in sails.js policy, and attach this policy to some controllers. But don't write you socket session into req.session! Otherwise, you'll get an error trying to respond to the client (original req.session is still used in some way). Instead, save it as req.socketSession or something like that.

please send a JSONP request from your application before sending a socket request,that will create a cookie and accepts socket requests.

You can do your initial login over the socket.post() instead of XHR, subsequent socket requests will be authorized.

alevkon,in the above mentioned method you have to implement the same in all the controllers,because you don't know which controller is accessed for the first time...but by sending just one jsonp request you can create a cookie between client and server,the same cookie is used until the next session.

Related

Best way to pass messages between 2 different protocol routes connected to same client

I have a http route /checkout which initiates a workflow process in Zeebe. The checkout route will return 200 response straight-away to calling client. Now, workflow will run for a while. So to push the response back to client after completion, I have a /sse separate route for server-sent events. Here, I will store all the client connection in a global map.
My doubt is how do I find the exact client to send the response back through sse once?
Example: Client A listening to /sse and calls /checkout endpoint which will return 200. The /sse has to return the response to client A after completion.
Currently, I thought of using cookie to identify the client. Is there a better way?
If you are already using cookies in your app than that's the way to go since the very purpose of cookies is identifying the client, so if you already have that, you should use it.
But if you rely on another authentication mechanism (like JWT), what you could do is using the url as a query.
So in the client instead of
let eventSource = new EventSource("/sse");
Do
let eventSource = new EventSource("/sse?authorization=jwt_token");
In the backend, you would validate that token, extract the Client ID and hit that global map with it to retrieve the corresponding connection.
(PS: instead of a global map, you should use a proper store, like redis, or an embedded key/value store like bbolt)

No persistent session when connecting to API on different host

I am sending a websocket connection to the API server on a different host:
new WebSocket("ws://localhost:3000")
Whereas my front end is hosted on localhost:8080.
Inside my API's websocket connection handler I'm able to set a key on the session (with Sinatra's enable :sessions) but every time I refresh the html page, the data is lost.
Is there some requirement for sessions that the front end share the same host as the server? Or is there some way I can get around this? By the way, the front end is running on a Webpack server (Node).
I also tried adding a cross_origin allowance for the API's root route http://localhost:3000 and then doing this in the client (this example in coffeescript):
$.get "http://localhost:3000", ->
new Websocket("ws://localhost:3000")
My thinking was that maybe the session needed to be "initialized" over http:// instead of ws:// but it didn't work either. The session didn't work for the $.get "http://localhost:3000" request either. Refreshing the page shows that the session clears each time.
As we've discussed in comments, you probably have a problem with 3rd party session cookies in the browser.
Here's a scheme that you could use to work around it.
Client makes webSocket connection for the first time.
Server sends a webSocket message back with sessionID in it.
Client stores sessionID in a first party cookie (e.g. a cookie in the host web page).
User hits refresh.
Web page checks to see if it has a webSocket session cookie in the cookies for the host page. If so, it constructs a URL for the webSocket connection that includes that session ID `new Websocket("ws://localhost:3000?session=xyslkfas")
When server accepts webSocket connection, it checks the query parameters to see if there is already a session being specified. If so and that session is still valid, it hooks up that connection to that session. If not, it creates a new session and goes back to step 2.

What's the best practice to renew a token for a WebSocket connection

This might be opinion based, but I still wonder is there a best practice since I'm almost clueless about websocket practices
I have a SPA that gets a JWT token from my own OP. It then uses that JWT to connect to other services I own using both REST and WebSockets.
As far as it goes for REST, it's pretty straightforward:
The REST API validates the JWT (sent in Authorization: Bearer ...) and provides access to the protected resource or responds with a 401, letting the SPA know it needs to request a new token.
Now with websockets :
During the load of the SPA, once I got a token, I'm opening a WS to my webservice. First message I send is a login_message with my JWT, which then I keep on server's websocket instance to know who's sending the messages.
Each subsequent message I receive, I validate the JWT to see if it's expired.
Once it has expired as far as I understand, I'm facing two options :
Drop the websocket with a token_expired error of some kind and force the browser to establish a new websocket connection once the token get refreshed.
Keep the websocket open, return an error message and send a new login message (once token is refreshed)
Don't use a login message but just send the JWT in each request.
Question : Which method would you recommend and why? In terms of security, and performance. Are there any other common practice I did not listed?
Quite an old question I've asked, so I'd be happy to share our chosen practice:
Once the client gets his JWT for the first time (when the application starts), a WebSocket is opened.
To authenticate the channel, we send a message that we define as part of our protocol, called authMessage which contains that JWT.
The server stores this data on the socket's instance and verifies it's validity/expiry before sending data down the wire or receiving from the client.
The token gets refreshed silently in web application minutes before it is expired and another authMessage is issued to the server (repeat from step 2).
If for whatever reason it gets expired before getting renewed, the server closes that socket.
This is roughly what we have implemented in our application (without optimization) and worked really well for us.
Oauth2 flow has two options to renew the token. As you said on of these options is prompt a message to the use to enforce a new login process.
The other option is to use the refresh_token in which you will avoid this login process to your user, every time the session expires and renew the token in a silent way.
In both case, you need to store the jwt in the client (commonly after login) and update it (after interactive login or silent regeneration). Localstorage, store or just a simple global variable are alternatives to handle the store and update the jwt in he client.
As we can see, jwt regeneration is solved following oauth2 spec and is performed at client side, SPA in your case.
So the next question is: How can I pass this jwt (new or renewed) to the external resources (classic rest api or your websocket)?
Classic Rest Api
In this case as probably you know, send the jwt is easy using http headers. In each http invocation we can send the old/valid jwt or the renewed jwt as header, commonly Authorization: Bearer ...
Websocket
In this case it's not so easy because according to a quickly review, there are not a clear way to update headers or any other "metadata" once the connection was established:
how to pass Authorization Bearer access token in websocket javascript client
HTTP headers in Websockets client API
What's more, there is no concept of headers, so you need to send this information (jwt in your case) to your websocket using:
protocols
var ws = new WebSocket("ws://example.com/path", ["protocol1", "protocol2"]);
cookies
document.cookie = 'MyJwt=' + jwt + ';'
var ws = new WebSocket(
'wss://localhost:9000/wss/'
);
simple get parameters
var ws = new WebSocket("ws://example.com/service?key1=value1&key2=value2");
Websocket only receive text
And according to the following links, websocket can extract header, get parameters and protocol just at the stabilization stage:
https://medium.com/hackernoon/implementing-a-websocket-server-with-node-js-d9b78ec5ffa8
https://www.pubnub.com/blog/nodejs-websocket-programming-examples/
https://medium.com/#martin.sikora/node-js-websocket-simple-chat-tutorial-2def3a841b61
After that, websocket server only receive text:
const http = require('http');
const WebSocketServer = require('websocket').server;
const server = http.createServer();
server.listen(9898);
const wsServer = new WebSocketServer({
httpServer: server
});
wsServer.on('request', function(request) {
const connection = request.accept(null, request.origin);
connection.on('message', function(message) {
//I can receive just text
console.log('Received Message:', message.utf8Data);
connection.sendUTF('Hi this is WebSocket server!');
});
connection.on('close', function(reasonCode, description) {
console.log('Client has disconnected.');
});
});
Send jwt in each request
Having analyzed the previous topics, the only way to send the new o renew token to your websocker backend is sending it in each request:
const ws = new WebSocket('ws://localhost:3210', ['json', 'xml']);
ws.addEventListener('open', () => {
const data = {
jwt: '2994630d-0620-47fe-8720-7287036926cd',
message: 'Hello from the client!'
}
const json = JSON.stringify(data);
ws.send(json);
});
Not covered topics
how perform a jwt regeneration using refresh_token
how handle silent regeneration
Let me know if you need this not covered topics.

Providing auth header with SockJS

I have a Spring MVC server that provides a bunch of REST endpoints as well as a websocket endpoint. Everything except the login endpoint requires authentication. I'm using JWT to authenticate requests coming from the client.
When the user logs in I'm returning an X-AUTH-TOKEN header, containing the JWT token. This token is then passed in the same header on every request to the server. This all works fine for the REST endpoints, but I can't figure out how to do this on the websocket.
I'm using SockJS, and when I open the connection:
var socket = new SockJS('/socket/updates', null, {});
This causes a GET request to /socket/updates/info?t=xxx which returns a 403 (as everything requires auth by default).
Ideally I'd simply send my X-AUTH-TOKEN header on any XHR requests SockJS makes, but I can't see any way of adding headers looking at the API.
Worst case I can change SockJS to do this, but I'm wondering whether this functionality has been deliberately left out? I know SockJS doesn't support cookies for security reasons but that's not what I'm trying to do.
Also, for the sake of a test I did allow the info endpoint to have anonymous access but then it 403's on a bunch of other endpoints - it feels more elegant to simply pass in auth details on these requests than poke holes in my server security.
Any help appreciated.
Cheers
You cannot set the header from SockJS. Not because SockJS does not have this functionality, but because browser makers don't expose this API to Javascript. See:
https://github.com/sockjs/sockjs-client/issues/196
For a workaround, see JSON Web Token (JWT) with Spring based SockJS / STOMP Web Socket.
client side:
stompClient.connect({headername:header}, function () {
setConnected(true);
stompClient.subscribe(request.topic, function (message) {
output(message.body);
});
});
server side :
StompHeaderAccessor accessor
= MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
String headervalue= accessor.getNativeHeader("your header name").get(0);

jsessionID not kept between jmeter http requests

I'm trying to do a simple load-test in a website which requires you to log in first.
I read the jmeter documentation about the cookie manager and looks like as long as I make all my requests within the same thread group where the cookie manager is the sessionID is shared among the http requests but is not my case.
The simple structure I have is:
Thread Group
HTTP Cookie Manager
HTTP Requests Defaults
Simple Controller
http request // POST method to authenticate in the site (this works fine, I get a session id)
http request // GET method (this one should use the same session id from the previous http requests but I get a new one instead)
I did this same web page flow in firefox using firebug to see the requests/responses and the session id is the same, the server doesn't send a new one so looks like the cookie manager is not keeping the same session id and using it for all the requests.
Can someone point me in the right direction? What I am doing wrong?
Check the get request sends the same jsessionid cookie in the request as the one returned in previous response.
If it's the case then check your login was fine, as it is probably root cause of issue

Resources