2

According to official spring documentation:

WebSockets reuse the same authentication information that is found in the HTTP request when the WebSocket connection was made. This means that the Principal on the HttpServletRequest will be handed off to WebSockets. If you are using Spring Security, the Principal on the HttpServletRequest is overridden automatically.

More concretely, to ensure a user has authenticated to your WebSocket application, all that is necessary is to ensure that you setup Spring Security to authenticate your HTTP based web application.

If I understood it correctly, this means that WebSocket is using the same channel for communication since the handshake, and thus the authentication should be made on the first connection.

However nowhere is stated how to actually authenticate the handshake in a standard secure way. As far as I am aware HTTP doesn't send an Authentication header while upgrading to the WebSockets so how it is done?

Do I really need to send authentication token in connection query, e.g

localhost:8080/ws?Auth=... 

and leave the security to HTTPS

Or do I need to authenticate the WebSocket after the connection is made e.g create my own handshake?

Is there any proper formal way to do it? I am using RAW websockets.

Thanks for the ideas/help.

Darlyn
  • 4,715
  • 12
  • 40
  • 90
  • `As far as I am aware HTTP doesn't send an Authentication header while upgrading to the WebSockets` what do you mean, upgrading? when you do the initial HTTP request, you provide a authentication header, or session cookie depending on the type of security you are running. – Toerktumlare Jan 25 '21 at 19:51
  • However, when you want to init WebSockets, you then send a request with an Upgrade header and then handshake for WebSockets begins. After that, you are not able to or to phrase it better client implementation of web socket does not let you send authentication header. I may be wrong about the details here tho. Feel free to correct me! – Darlyn Jan 25 '21 at 22:05
  • the RFC specifies the following https://tools.ietf.org/html/rfc6455#section-10.5 and the opning handshake is a plain GET request, nothing fancy, so thats where you include a header, cookie or whatever you need. https://tools.ietf.org/html/rfc6455#section-1.3 – Toerktumlare Jan 25 '21 at 22:21
  • hmm, interessting ,however you still use you client libraries for WebSockets and neither of them let you specify auth headers. For example i use native JS websocket() for this. – Darlyn Jan 25 '21 at 22:26
  • well there seems to be a missmatch between the javascript world and the backend world. RFC says it is allowed, but there is no implementation as you say. I found this https://stackoverflow.com/questions/4361173/http-headers-in-websockets-client-api – Toerktumlare Jan 25 '21 at 22:48

1 Answers1

0

I personally use STOMP but with STOMP (basically a framework for communications on raw WebSocks), the session cookie (from spring security) is send with any message down the socket.

You can use the StompAccessorHeader like:

  @MessageMapping("/agents/start")
    public void start(StompHeaderAccessor stompHeaderAccessor) {
        log.info("Subscriber Start! {}-{}", stompHeaderAccessor.getUser() != null ? stompHeaderAccessor.getUser().getName() : "ANON", stompHeaderAccessor.getSessionId());
        mysessionstore.addSessionId(stompHeaderAccessor.getSessionId());
    }

With not using the STOMP framework there may be a way to read the SessionCookie sent per request on the raw socket?

I am not 100% sure but I am guessing you are using the TextWebSocketHandler implmentation with the:

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage textMessage)

I can see in the source code for WebSocketSession you should be able to get your principal authenticate user there:

https://github.com/spring-projects/spring-framework/blob/0de2833894c24c1e70bde991bad171435c6ecac2/spring-websocket/src/main/java/org/springframework/web/socket/WebSocketSession.java#L37

So you authenticate like normal REST like POST "/login" and then that session should be valid for the websockets as well.

You may be able to auth via the socket? You'd have to like make your own socket endpoint to take their credentials and do SecurityContextHolder.getContext().setAuthentication(myAuthUserToken) but maybe that will then pass a session cookie back? You'd have to test this ofc as I am unsure if it would work shrug.

I personally then make a "store" (a singleton or redis) that holds the user principal and the socketSessionId so I can then match a user to a socket.

You could say store them in a singleton with a HashMap<String,String> userPrincipalNameToSocketSessionId as a crude way store which socket session belongs to which user.

eg.

A bit like

@Override
protected void handleTextMessage(WebSocketSession session, TextMessage textMessage){
  MySessionStore.addSessionToMap(session.getPrincipal(),session.getId());
  log.info("Added user {} websocket session {} to the store.",session.getPrincipal(),session.getId());
}


public */MySingletonClass*/ MySessionStore{

@Getter
public static volatile HashMap<String,String> userPrinciapalToSocketMap = new HashMap<>();

//Method to add to map here
public synchronized static addToMap(String principalName,String webSocketSessionId){
...Adds to the map.
}

JWT Stateless Auth System wanting a Socket Session?

As far as I can guess with this one...unless there is a lot of overriding and practically forking/extending lots of Spring classes...

You could make a controller:

Http GET => "/websocket-ticket" which would return a signed token with the user's principal/username/id for the UX to then pass as a first message after websocket connect.

The socket handler TextMessageHandler can check the signaute of the token and add it to your HashMap<String,String> principalUserToSessionId store.

The security issue (unlikely but it is there):

An attacker with XSS could snoop that token and hijack that websocket session. Maybe you win on a race condition (i.e. the MITM takes longer and the token is single use...more wonderful implementation...you now also need a "websocket-ticket-consumed" store...).

https://devcenter.heroku.com/articles/websocket-security

I am feeling this is all heading to the X/Y problem. Why are you using JWTs for Auth?

Jcov
  • 2,122
  • 2
  • 21
  • 32
  • "So you authenticate like normal REST like POST "/login" and then that session should be valid for the websockets as well." I am doing this - authenticating and setting SecurityContextHolder.getContext().setAuthentication(myAuthUserToken) ( i am using JWT authentication ). However this SET the authToken into the context. But next, when websocket tries to init connection, new thread is used for this connection ( internally in spring ) and thus Context does not hold the authToken since it uses ThreadLocal for it. And that is root of my problem. – Darlyn Jan 25 '21 at 22:10
  • then you need to show us your code, you should not be needing to set the SecurityContextHolder unless you are doing some crazy custom stuff, why not use the built in authentication filters that spring provides? – Toerktumlare Jan 25 '21 at 22:24
  • You can send your JWT down the socket as the first message after authenticating, have the `TextMessageHandler` use that/validate that JWT to know which user initiated that socket session. Ill add this as well...as it screams another odd security concern (why does your front end JS have access to a token... :|). – Jcov Jan 25 '21 at 23:44
  • hmm, what do you mean why it has access? Doesnt it needs to be sent with every request for authentication? ( Yes spring is setting principal for httprequest or session - not sure which one but they are are still threadlocaly stored, so when the request is picked up by different thread the user would need to re-login, and with all of these the point of JWT is useless) Could ye explain? – Darlyn Jan 25 '21 at 23:55
  • No. Because once you know that websockets session ID you can store it against the user's id/username. Store it in Redis/Singlton/SQL...whatever fits best. A socket stays open till closed. The socket session id is the same for its whole lifetime. Not per message sent down it. – Jcov Jan 25 '21 at 23:56
  • I also don't see how you have a session but also are using JWTs for your auth. Something smells off in your security/auth/authz implementation to be honest. – Jcov Jan 25 '21 at 23:59
  • You should store JWT authorization tokens from a browser in the HttpOnly cookie store. Your JS should have 0 access to them. This is another whole topic and is commonly "skipped" with developers storing JWTs in all kinds of bad places (local storage for example). – Jcov Jan 26 '21 at 00:02
  • oh right, i store the session in container for further actions, identified by users id. I use JWT for other authorization stuff which are not trough websockets. The websocket isn't initialized since the start of the application but when user needs to connect to the chat. So i am actually having REST API and websockets in app, not everything is going trough websockets. Thanks for pointing with the storing of JWT token, will take look at it. – Darlyn Jan 26 '21 at 00:08
  • Right sort of makes sense. I'd personally not use JWTs for this app. It sounds like a stateful app to me. Users log in, have profiles/hit end points and chat etc. All sounds quite stateful to me. You can use JWTs and it is up to the developer but you will run in to these issues and end up with the "hack" like iv'e mentioned in various places. Basically you are authenticating, giving them a JWT but then now trying to hold some information server side about which JWT belongs to who and keep some state...screams session to me. – Jcov Jan 26 '21 at 00:19