Basic Auth on Java Websockets via StompJS in Angular

I currently have an Angular app with a Spring Boot backend where the user can log in using a Basic auth token and send/receive REST responses, which all works fine. I have also added WebSocket functionality to the app, which I would like to authorise using the same token if the user is already logged in (with guards in place to prevent anonymous users).

Currently my WebSecurityConfigurerAdapter config is:

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf()
            .disable()
            .authorizeRequests()
            .antMatchers(HttpMethod.OPTIONS, "/**")
            .permitAll()
            .antMatchers("/auth/**")
            .permitAll()
            .antMatchers("/ws/**")
            .authenticated()
            .anyRequest()
            .authenticated()
            .and()
            .cors()
            .and()
            .httpBasic();
    }

With /auth being my controller for registration, login etc. and /ws as the WebSocket endpoint.
My WebSocket code is standard:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
            .setAllowedOrigins("http://localhost:4200")
            .withSockJS();
    }
    
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        registry.setApplicationDestinationPrefixes("/app")
            .enableSimpleBroker("/topic");
    }
}

Problem: Login Alert

With the code as it is, when I try to connect to the WebSocket via the Angular app, I get an alert login form where the user would have to enter their credentials once again. I have about 10 seconds to log into the alert before it closes and the connection is refused:

https://i.stack.imgur.com/zDh5P.png

Ideally I’d like to use the Basic auth token when connecting to the backend and not get the alert login at all. My StompJS code currently looks like this:

  _connect() {
    console.log("Connecting as user: (" + this.authenticationService.getLoggedInUserName() + ")");
    console.log("Using basic token: (" + this.authenticationService.getLoggedInUserToken() + ")");
    if (this.authenticationService.isUserLoggedIn()) {
      let ws = new SockJS(this.webSocketEndPoint);
      this.stompClient = Stomp.over(ws);
      const _this = this;
      _this.stompClient.connect(
        {
          'Authorization': this.authenticationService.getLoggedInUserToken()
        },
        function (frame) {
          _this.stompClient.subscribe(_this.board, function (sdkEvent) {
            _this.onDataReceived(sdkEvent);
          });
        }, this.errorCallBack);
      this.connected = true;
      this.emitConnected();
    } else {
      alert("Must be logged in to connect.");
    }
  }

This attaches the token to the header. I’ve found code that should work for Jwt Tokens, where I’m currently trying to simply print out the token to console to confirm that the header works, but the print statements only happen after the user has already logged in via the alert pop up, so it doesn’t seem to be the correct solution even if I was able to use the token to authorise. Here’s the code for that so far:

@Configuration
public class WebSocketSecurityConfig extends AbstractSecurityWebSocketMessageBrokerConfigurer {
    
    @Override
    protected void configureInbound(MessageSecurityMetadataSourceRegistry messages) {
        messages.anyMessage().authenticated();
    }
    
    @Override
    protected boolean sameOriginDisabled() {
        return true;
    }
}

And:

@Configuration
@EnableWebSocketMessageBroker
@Order(Ordered.HIGHEST_PRECEDENCE + 99)
public class WebSocketAuthenticationConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        // This line prints when the app is loaded/refreshed
        System.out.println("Entering configureClientInboundChannel...");
        registration.interceptors(new ChannelInterceptor() {

            @Override
            public Message<?> preSend(Message<?> message, MessageChannel channel) {
                System.out.println("Entering preSend...");
                StompHeaderAccessor accessor = 
                        MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
                if (StompCommand.CONNECT.equals(accessor.getCommand())) {
                    List<String> authorisation = accessor.getNativeHeader("Authorization");
                    
                    String type = authorisation.get(0).split(" ")(0);
                    String basicToken = authorisation.get(0).split(" ")(1);
                    // These lines only print AFTER the user has logged in via the alert login form.
                    System.out.println("Intercepted Token of type " + type);
                    System.out.println("Token String: " + basicToken);
                }
                return message;
            }
            
        });
    }
}

I’ve had a look at other proposed solutions but they seem to lead to dead ends or fail to work before the alert message is already loaded.