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);
Related
How is CSRF over WebSockets expected to work?
I am sending a CSRF Token as STOMP header on the Connect but the org.springframework.security.messaging.web.csrf.CsrfChannelInterceptor does not seem to find it. I tried to dig into the sources and I seem to understand that the CSRF Token is expected to be supplied with the initial websocket HTTP Handshake and is then stored inside some sort of session or repository where it is taken out. The job seems to be done by org.springframework.security.messaging.web.socket.server.CsrfTokenHandshakeInterceptor.
But - my HTTP Handshake does not supply any CSRF token - Its the same as with any other Header like Authorization - I cant supply any custom header together with the initial Handshake! Sending it as _csrf Query Parameter doesn't help either. It's a GET request and doesn't require CSRF per default anyway.
How is CSRF supposed to work with Websocket STOMP over HTTP?
Inside CsrfTokenHandshakeInterceptor whike processing
There is already an issue open for spring-security which adresses it. There is also a workaround supplied in that issue https://github.com/spring-projects/spring-security/issues/12378
My code example with the workaround can be found here: https://github.com/OlliL/spring-boot-stomp
It could also serve as an example implementation for JWT + CSRF with STOMP Websockets maybe (I am no expert ;))
I'm building an MVC web app that uses the openID Connect hybrid flow to authenticate with Identity Server 3. The MVC web app contains jQuery scripts to get async JSON data from een ApiController. That ApiController is part of the same MVC web app.
I don't want that everyone is able to access the data from the API, so I want to secure the API as well. I added an [authorize] attribute to the ApiController. When requesting the API with a JQuery ajax request I get the following error message:
XMLHttpRequest cannot load
https://localhost:44371/identity/connect/authorize?....etc.
Response to preflight request doesn't pass access control check: No
'Access-Control-Allow-Origin' header is present on the requested
resource. Origin 'http://localhost:13079' is therefore not allowed
access. The response had HTTP status code 405.
But, when I do a request to the API method directly in browser, I will be correct redirected to the Login page of Identity Server..
So, what's exactly the problem here? I read something about that requesting the /authorize endpoint is not allowed via 'back-channel', but I don't understand what's the difference between 'front-channel' and 'back-channel'. Is it possible that I mixed up the wrong OAuth flows? Is the Hybrid flow not the correct one maybe?
I also find out that the API is often a seperate app, but is it always neccessary / best-practice to build a seperate API app that for example requires a bearer token?
Please point me in the right direction about this.
The authorize method on your identity server does not allow ajax calls. Even specifying CORS headers is not going to help you in this particular case. Perhaps you could return a forbidden response instead of a redirect and manually redirect the client to the desired location via window.location
You need to allow your IdentityServer to be accessed from other domains, this is done by allowing "Cross Origin Resource Sharing" or CORS for short. In IdentityServer the simplest way to allow this is in your Client configuration for your Javascript Client, see this from the IdentityServer docs on CORS:
One approach to configuing CORS is to use the AllowedCorsOrigins collection on the client configuration. Simply add the origin of the client to the collection and the default configuration in IdentityServer will consult these values to allow cross-origin calls from the origins.
The error you're seeing is the browser telling you that when it asked IdentityServer if it allows requests from your Javscript client, it returned a response basically saying no, because the origin (http://localhost:13079) was not specified in the "Access-Control-Allow-Origin" response header. In fact that header wasn't in the response at all meaning CORS is not enabled.
If you follow the quickstart for adding a JavaScript client from the docs here all the necessary code is detailed there that you need for the Client config and to setup IdentityServer to allow CORS.
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.
I am trying to figure out what the best way is to pass an oauth bearer token to a websocket endpoint.
This SO answer suggests to send the token in the URL,
however this approach has all the drawbacks of authenticating via the URL. Security implications discussed here
Thus i was wondering what would be the drawbacks to use the subprotocols to pass the token to the server ? i.e. instead of treating the requested subprotocols as a list of constants. Send at least one subprotocol that follows a syntax like for example: authorization-bearer-<token>
The token would end up in a request header.
The server while processing the subprotocols would be able to find and treat the token easily with a bit of custom code.
Since passing subprotocols should be supported by a lot of websocket implementations, this should work for a lot of clients.
This worked for me, I used this WebSocket client library.
You need to send OAUTH token via the Websocket Header, Below is the code, hope this is helpful.
ws = factory.createSocket("wss://yourcompleteendpointURL/");
ws.addHeader("Authorization", "Bearer <yourOAUTHtoken>");
ws.addHeader("Upgrade", "websocket");
ws.addHeader("Connection", "Upgrade");
ws.addHeader("Host", "<YourhostURLasabovegiveupto.com>");
ws.addHeader("Sec-WebSocket-Key", "<Somerandomkey>");
ws.addHeader("Sec-WebSocket-Version", "13");
ws.connect();
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.