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:
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?
The most secure option is for the authorization server to issue a new refresh token each time one is used. This is the recommendation in the latest Security Best Current Practice which enables authorization servers to detect if a refresh token is stolen.
The refresh token is set with a very long expiration time of 200 days. If the traffic to this API is 10 requests/second, then it can generate as many as 864,000 tokens in a day.
The authorization server can contain this risk by detecting refresh token reuse using refresh token rotation. If your application uses refresh token rotation, it can now store it in local storage or browser memory.
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:
What's more, there is no concept of headers, so you need to send this information (jwt in your case) to your websocket using:
var ws = new WebSocket("ws://example.com/path", ["protocol1", "protocol2"]);
document.cookie = 'MyJwt=' + jwt + ';' var ws = new WebSocket( 'wss://localhost:9000/wss/' );
var ws = new WebSocket("ws://example.com/service?key1=value1&key2=value2");
And according to the following links, websocket can extract header, get parameters and protocol just at the stabilization stage:
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.'); }); });
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); });
Let me know if you need this not covered topics.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With