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 ;))
Related
What I want to do
Calling an URL which is proxied by the oauth2 proxy. The oauth2 proxy should perform an authorization code flow in case no authentication is available. In case there is already an authentication available, the access token should be set to the Authorization Header in the request which is forwarded to the upstream.
What I tried
According to the documentation I'd expect that, when setting --pass-authorization-header the token which is requested should be added to the authorization header.
I also experimented with --pass-access-token which should set an X-Forwarded-Access-Token header.
I couldn't see this header at my service either.
Could someone explain to me what I'm doing wrong?
I found the solution.
This post on a github issue lead me to my mistake.
I did misunderstand what the request is and what the response is and how to handle them using nginx ingresses.
If you are using OAuth2-Proxy with a Kubernetes ingress using nginx subrequests (https://kubernetes.github.io/ingress-nginx/examples/auth/oauth-external-auth/) the data that comes back to nginx is actually an HTTP response, so you will need to use HTTP Response headers (the --pass-* options configure request headers to the upstream).
Try --set-authorization-header and then you need to use this annotation to have the Kubernetes take the subrequest response header and add it to the proxied request header: nginx.ingress.kubernetes.io/auth-response-headers
https://kubernetes.github.io/ingress-nginx/user-guide/nginx-configuration/annotations/#external-authentication
I am using websockets to communicate between server and client. I followed this documentation to implement websocket dependecy. Now when I try to connect via example client page it produces the following error:
Access to XMLHttpRequest at '....' from origin 'null' has been blocked by CORS policy: The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
I have tried adding .setAllowedOrigins("*") to WebSocketConfig but it didn't fix this. What is the solution?
As the error message says you are sending a request with credentials set to include, I guess you are sending some kind of token/access data with your request.
When doing so your server must respond with the correct origin. If you are in development try setting your localhost. For example .setAllowedOrigins("http://localhost:8080") or what ever you are using. In production you need to replace that with your deployment url.
You might find success with .setAllowedOriginsPattern("*").
See the following documentation for more stricter patterns.
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 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);
Today I faced the problem that (suddenly) the SAP Gateway stopped acceppting CSRF tokens issued by himself.
Checked the network trace, everything is fine. The Client gets a token using GET Method and the HTTP Header
X-CSRF-Token: Fetch
receiving one, followed by an immediate POST request using the received Token and getting a 403 Forbidden status with response Body "CSRF Token could not be verified" (or similar)
By default, the CSRF Protection is only enabled over HTTPS in SAP Netweaver Gateway. How to enable CSRF over HTTP (and why not to do so) is described in the following SAP Note:
1896961 - HTTP/HTTPS Configuration for SAP NetWeaver Gateway
The important bit of the Note:
... set the instance profile parameter login/ticket_only_by_https to 0...