Vaadin heartbeat - CORS redirect OIDC issue

We currently have an issue with Vaadin heartbeat function: it seems that he does not works properly.

As far as we know, Vaadin heartbeat function is working like this:

  • Vaadin sends a ping to the backend every 5 minutes when the app is idle (not being worked on and no mouse move or keyboard action)
    • this ping has the session cookies on it.
      • when this hits the backend service it validates the authentication token
        • if its not expired yet, no issues it goes through
        • if token is expired (validity is 1h in our setup) it tries to redirect to OIDC (Connection Lost triggered here)
          • β†’ this gets blocked on the vaadin frontend app due to security default configs
          • β†’ adds a temporary cookie
          • β†’ retries the operation
            • as it retries more cookies get added until reaching the size limit and triggering 431 HTTP response.

Issue description:

It looks like an issue with the JavaScript initiated heartbeat that occurs every 5 min, it seems that it doesn’t have the CORS headers setup properly, if it were from any other Quarkus app, we expect it to work fine (example redirect during login)

At the moment, we only increased the token session to 8 hours in order to avoid this issue.

It will be great is there any solution to solve that issue which is a blocking point for us at the moment.

Our setup:

Java JDK 21
Quarkus 3.11.1
Vaadin 24.3.10

Some screens regarding Vaadin heartbeat issue:

HTTP status code is a temporary redirect. Do you have a reverse proxy or firewall in front of the Vaadin application?

To me this sound like that the redirect of XHR is not the right thing to do anyway, but it should instead reload the page itself (resulting into page load redirected to OIDC).

We are experiencing pretty much the same issue in our setup.

  • a users refresh token expires
  • the next heartbeat triggers authentication check
  • our backend (Wildfly Elytron) sends a 302 back
  • vaadin follows the redirect to our identity provider (AWS Cognito)
  • the browser blocks the the response, due to missing CORS header received from Cognito

From our understanding, the javascript should do a proper redirect (but that is not possible?).
Our question is, is this issue solved? How could one instruct vaadin to make a proper redirect in this case? Or, as Sami suggested, how could we force a page reload instead?

Greetings,
to follow up on this issue, we have come to a workaround that prevents this redirect cors issue for the most part.
We simply override the XHR and check if the request was a heartbeat. We then use a fetch that does not follow the redirect, and if it fails, we reload the page.

Code for reference
(function() {
    const originalOpen = XMLHttpRequest.prototype.open;
    const originalSend = XMLHttpRequest.prototype.send;

    XMLHttpRequest.prototype.open = function(method, url, async, user, password) {
        this._url = url;
        this._method = method;
        originalOpen.call(this, method, url, async, user, password);
    };

    XMLHttpRequest.prototype.send = function(body) {
        if (this._url && this._url.includes('v-r=heartbeat')) {
            console.debug(`[Heartbeat Interceptor] Heartbeat detected – using fetch instead of XHR`);
            fetch(this._url, {
                method: this._method || 'GET',
                redirect: 'manual'
            }).then(response => {
                if (response.status >= 200 && response.status < 300) {
                    console.debug(`[Heartbeat Interceptor] ${response.status}: Heartbeat successful`);
                    originalSend.call(this, body);
                } else {
                    console.warn(`[Heartbeat Interceptor] ${response.status}: Heartbeat unsuccessful, reloading page...`);
                    window.location.reload();
                }
            }).catch(error => {
                console.error(`[Heartbeat Interceptor] Failed Heartbeat:`, error);
                console.warn(`[Heartbeat Interceptor] Reloading page...`);
                window.location.reload();
            });
            return;
        }
        originalSend.call(this, body);
    };
})();
1 Like