I’m building a WebSocket-based microservice architecture using Spring Cloud Gateway and STOMP WebSockets.
Users log in through the frontend, and the backend sets:

  • access_token (HttpOnly, Secure, SameSite)

  • refresh_token (HttpOnly)

The frontend cannot read these cookies (as expected).
Everything works fine for normal HTTP requests — the HttpOnly cookies are automatically sent with each request to the gateway.

🚨 Problem

When I try to connect to the WebSocket endpoint:

const client = new Client({
    webSocketFactory: () => new WebSocket('ws://localhost:8090/ws-chat'),
    connectHeaders: {
        Cookie: `access_token=${TOKEN}` // <-- this is impossible in real browser
    }
});

Cookie is always null on the backend gateway:

@Override
public GatewayFilter apply(Config config) {
    return (exchange, chain) -> {
        System.out.println(exchange.getRequest().getCookies());
        // Result: {}
    };
}

Because the frontend cannot access HttpOnly cookies, I cannot attach the access token to the WebSocket handshake request.

So inside my gateway filter this fails:
String accessToken = request.getCookies().getFirst("access_token").getValue();

// access_token is null => NullPointerException

❓ Question

How do real apps like WhatsApp, Telegram, etc. handle WebSocket authentication when the token is stored in HttpOnly cookies and the frontend cannot read it?

Specifically:

  1. How can I securely authenticate WebSocket connections through the gateway?

  2. How can I pass authentication data during CONNECT handshake when browser JS cannot read cookies?

  3. Is there a pattern (e.g., temporary WS token, session handshake, etc.) that I should implement?

1 Reply 1

SameSite cookies

As you already stated Cookies should be sent with each HTTP request and you don't need to access that HttpOnly cookie from the frontend. You should be able to access the Cookie header on the backend. This already answers your 2nd question. You don't need to do anything with JavaScript.

Since you have also set SameSite attribute on the cookie, it will be sent with the HTTP request if your client and the server are the same site.

You can refer to the link to learn what a "site" means but TLDR is:

According to this definition, support.mozilla.org and developer.mozilla.org are part of the same site, because mozilla.org is a registrable domain.

One more gotcha

Another case is, the WebSocket protocol starts as an HTTP request. The simple flow is:

  1. Client sends HTTP request to backend with Connection: upgrade , Upgrade: websocket and couple of other headers. This means server needs to switch to WebSocket protocol.

  2. If server can upgrade to WebSocket protocol, then will response with HTTP 101 with couple of other necessary headers.

In this case after the switch is happened I don't know if you will have access to HTTP headers. This means authentication needs to be done when the very first HTTP request is sent to the server (namely during WebSocket handshake). After switching protocols the client and server will use WebSocket protocol only.

Check if GatewayFilter is doing its filter on the first HTTP request. I am not a SpringBoot guy, so I don't know the details about the lifecycle of the request. Your customGatewayFilter might happen in a lot of different places througout request lifecycle.

Your Reply

By clicking “Post Your Reply”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.