Blog

Silent relogin in Keycloak fails (because of the Dragon browser extension)

In the last few days, I had the pleasure of debugging a problem with the silent (re)login feature provided by Keycloak.js.

The symptoms

The application we develop is an Angular application (still running with Zone.js). Everything worked flawlessly in our development and test environments, and everything was also fine in the production environment on most of our customer's workstations. However, on some workstations, the Keycloak client did not return from silent relogin until the user, for example, pressed a key on their keyboard.

The first indicator

So we started a remote debugging session. On the workstations where silent relogin hung, we saw the following error in the browser console: Console Error. Although our code is minified in production and does not contain source maps, Edge's support for formatting minified code helped us debug the issue. We saw that the offending value delivered to the Keycloak client looked like this: Invalid Data

What the heck! Where does this come from? The Keycloak library only processes messages delivered by the hidden iframe it inserts itself (see the following code block, as of this writing). The only code posting messages from this iframe is an HTML page that we deliver.

The strong indicator

Once we had the invalid value, a Google search pointed us toward Nuance Dragon, a Chrome extension from Nuance: Google Search

When we checked the extensions installed on the affected systems, we found this extension on all of them. Disabling it temporarily fixed the problem.

How to fix this for now

Once we had identified the problem, we had multiple options for addressing it. We settled on patching the Keycloak library and bundling the patched library in the build (as of this writing, this is keycloak-js v26.2.4).

The patch looks like this:

diff --git a/node_modules/keycloak-js/lib/keycloak.js b/node_modules/keycloak-js/lib/keycloak.js
index 861fef8..fb9dae2 100755
--- a/node_modules/keycloak-js/lib/keycloak.js
+++ b/node_modules/keycloak-js/lib/keycloak.js
@@ -985,17 +985,22 @@ export default class Keycloak {
        * @param {MessageEvent} event
        */
       const messageCallback = async (event) => {
-        if (event.origin !== window.location.origin || iframe.contentWindow !== event.source) {
+        if (event.origin !== window.location.origin || iframe.contentWindow !== event.source || typeof event.data !== 'string') {
           return
         }
 
-        const oauth = this.#parseCallback(event.data)
-
         try {
-          await this.#processCallback(oauth)
-          resolve()
-        } catch (error) {
-          reject(error)
+          const oauth = this.#parseCallback(event.data);
+
+          try {
+            await this.#processCallback(oauth)
+            resolve()
+          } catch (error) {
+            reject(error)
+          }
+        } catch(error) {
+          console.warn('Skipping further processing. Provided data via postMessage does not match expectation of keycloak. Provided data is:', error);
+          return;
         }
 
         document.body.removeChild(iframe)

How to fix for the long term

As it turns out, others had already run into this problem and filed a GitHub issue. However, the proposed fix would not help us because the Dragon extension posts a string, so we would still run into the problem.

So we proposed a different patch, which allows developers to specify a prefix to include with messages sent from the iframe.

Takeaway

What did we learn from this? You always have to take browser extensions into consideration, as they can do things that would be impossible in a normal setup (such as sending a message from an iframe you control). I do have serious questions about what the Dragon extension is doing, though I doubt they'll change it anytime soon. You can't even file a bug report with them.

published on
Previous
NULL vs UNDEFINED