8000 Mitigate Unexpected RESTART_AUTHENTICATION_ERROR on iOS Browsers by t-tani · Pull Request #34802 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

Mitigate Unexpected RESTART_AUTHENTICATION_ERROR on iOS Browsers - #34802

Closed
t-tani wants to merge 1 commit into
keycloak:mainfrom
t-tani:issue/gh-33071-RESTART_AUTHENTICATION_ERROR-in-iphone-devices
Closed

t-tani wants to merge 1 commit into
keycloak:mainfrom
t-tani:issue/gh-33071-RESTART_AUTHENTICATION_ERROR-in-iphone-devices

Conversation

@t-tani
@t-tani t-tani commented Nov 10, 2024 •
Copy link
Copy Markdown

@jonkoops

I have found another approach to mitigate the RESTART_AUTHENTICATION_ERROR in iOS browsers, so I opened this PR.

Issue

Closes #33071

Summary of Changes

This PR introduces a 2000ms delay in the checkCookieAndSetTimer function to address authentication errors on iOS browsers. This fix make amends for the unreliable beforeunload event in iOS browsers, which can cause RESTART_AUTHENTICATION_ERROR.

Key points

  • Adds a 2000ms delay before redirect triggered by checkCookieAndSetTimer
  • Provides a temporal window, enabling the browser to dispose of the entire context, including the timeout event, before proceeding with the redirect when beforeunload event doesn't work.

I have confirmed that on my iPhone and iPad using Safari/Chrome with Keycloak 26.0.5, errors do not occur unless there are significant network speed issues or loading delays. However, it's important to note that this is only a mitigation for the unreliable beforeunload event, so it cannot completely eliminate the issue.

Related PR

PR #34709

Signed-off-by: t-tani <31282248+t-tani@users.noreply.github.com>
@t-tani
t-tani requested review from a team as code owners November 10, 2024 15:19
@mposolda mposolda self-assigned this Nov 11, 2024
@mposolda
Copy link
Copy Markdown
Contributor

@graziang @jonkoops Could you please review this PR?

@graziang graziang left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks @t-tani for the PR! As you mentioned, this solution mitigates the issue but doesn’t completely resolve it. Unfortunately, I can’t replicate the issue on an iOS device right now and test the absence of the KC_RESTART cookie. Based on the comments, it seems that the browser is being redirected to the restart URL by authChecker.js and the KC_RESTART cookie is missing. However, it doesn’t make sense to me that the browser should be redirected to the restart URL after login on the same page there could be some kind of block on iOS causing these side effects. So, before moving forward with this solution, it would be better to understand what might be causing the root issue.

@t-tani
t-tani commented Nov 12, 2024 •
Copy link
Copy Markdown
Author

@graziang
Thank you for the review.

Root Cause:
The core issue stems from inconsistent behavior of the "beforeunload" event across different browsers, particularly between WebKit-based browsers (like Safari on iOS) and other browser engines. Some functionality in authChecker.js is depending on "beforeunload" event which is not reliably triggered in all browsers.

Current Implementation:

  1. We use a recursive timeout event that checks every 2000ms for the presence of the KEYCLOAK_SESSION cookie to handle automatic login.
  2. This check determines if a session has been issued in other tabs and, if so, sends a restart request.
  3. To prevent unnecessary restart requests when authentication occurs in the same tab/page, we attempt to remove this timeout event using the beforeunload event.

Problem:
In iOS browsers (which use the WebKit engine), the beforeunload event doesn't function as expected. This leads to:

  • Failure to remove the timeout event
  • Unnecessary restart requests being sent, after KEYCLOAK_SESSION is set and KC_RESTART is unset even when authentication occurs in the same tab before the dispose of browser context triggered by the redirect

Testing:
I've created a test site based on authChecker.js to demonstrate the unstable behavior of "beforeunload" across the browsers. On PC browsers, the beforeunload event fires correctly. However, on iOS browsers, it fails to fire.

test site: https://t-tani.github.io/test-beforeunload-event/
repository: https://github.com/t-tani/test-beforeunload-event

result1: behavior of beforeunload event on PC browser (chrome on ubuntu 22.04)
image

result2: behavior of beforeunload event on iOS browser (safari on on iOS 18.01)
image

Potential Solution:
While documenting this issue, I've considered an alternative approach: removing the timeout event on client side when initiating any authentication request. This could potentially address the problem more fundamentally.

Next Steps:
When time permits, I will test this new approach and update this thread with the results.

@t-tani
t-tani commented Nov 12, 2024
Copy link
Copy Markdown
Author

To be honest, I believe the essential problem lies not in Keycloak's implementation, but in how WebKit handles the beforeunload event. However, in the Keycloak instance I manage, we receive a large number of authentication requests from iOS browsers, and this issue is affecting the user experience. Therefore, I'm hoping we can implement some kind of workaround on the Keycloak side to address this problem.

@rmartinc
Copy link
Copy Markdown
Contributor

One question @t-tani, reading some info about this it seems thatthe problem is that iOS does not manage the event beforeunload and it seems that pagehide is the correct way to go here (for example reading this post). I cannot test because I don't have any iOS device, but what happens if you use both events? Something like this:

diff --git a/themes/src/main/resources/theme/base/login/resources/js/authChecker.js b/themes/src/main/resources/theme/base/login/resources/js/authChecker.js
index 8507e71663..e5fdcb58a8 100644
--- a/themes/src/main/resources/theme/base/login/resources/js/authChecker.js
+++ b/themes/src/main/resources/theme/base/login/resources/js/authChecker.js
@@ -6,12 +6,8 @@ let timeout;
 
 // Remove the timeout when unloading to avoid execution of the
 // checkCookiesAndSetTimer when the page is already submitted
-addEventListener("beforeunload", () => {
-  if (timeout) {
-    clearTimeout(timeout);
-    timeout = undefined;
-  }
-});
+addEventListener("beforeunload", clearTimeoutOnEvent);
+addEventListener("pagehide", clearTimeoutOnEvent);
 
 export function checkCookiesAndSetTimer(loginRestartUrl) {
   if (initialSession) {
@@ -42,6 +38,13 @@ export function checkAuthSession(pageAuthSessionHash) {
   }, AUTH_SESSION_TIMEOUT_MILLISECS);
 }
 
+function clearTimeoutOnEvent(event) {
+  if (timeout) {
+    clearTimeout(timeout);
+    timeout = undefined;
+  }
+}
+
 function getKcAuthSessionHash() {
   return getCookieByName("KC_AUTH_SESSION_HASH");
 }

Does this change work for you in iOS?

@t-tani
t-tani commented Nov 12, 2024 •
Copy link
Copy Markdown
Author

@rmartinc Thank you for the suggestion. I have tested the scenario setting both pagehide and beforeunload events to clear the timeout but it does not solve the problem. Here is what actually occurred with Keycloak 26.0.5 and overwriting the authChecker.js w 8000 ith local proxy(burpsuite).

image

Here I set both events in the authChecker.js and tested with Safari browser (iOS 18.0.1) but unexpected restart request was called. I have not debugged the pagahide event with web inspector yet but I suppose that pagehide event is fired too late to avoid the restart request.

@rmartinc
Copy link
Copy Markdown
Contributor

And using visibilitychange like in this diff:

--- a/themes/src/main/resources/theme/base/login/resources/js/authChecker.js
+++ b/themes/src/main/resources/theme/base/login/resources/js/authChecker.js
@@ -6,10 +6,10 @@ let timeout;
 
 // Remove the timeout when unloading to avoid execution of the
 // checkCookiesAndSetTimer when the page is already submitted
-addEventListener("beforeunload", () => {
-  if (timeout) {
-    clearTimeout(timeout);
-    timeout = undefined;
+addEventListener("beforeunload", clearTimeoutOnEvent);
+addEventListener("visibilitychange", (event) => {
+  if (document.visibilityState == 'hidden') {
+    clearTimeoutOnEvent(event)
   }
 });
 
@@ -42,6 +42,13 @@ export function checkAuthSession(pageAuthSessionHash) {
   }, AUTH_SESSION_TIMEOUT_MILLISECS);
 }
 
+function clearTimeoutOnEvent(event) {
+  if (timeout) {
+    clearTimeout(timeout);
+    timeout = undefined;
+  }
+}
+
 function getKcAuthSessionHash() {
   return getCookieByName("KC_AUTH_SESSION_HASH");
 }

Sorry, but I cannot test iOS. 😄

@t-tani
t-tani commented Nov 12, 2024
Copy link
Copy Markdown
Author

Unfortunately, visibilitychange does not make difference here.

image

@jonkoops
Copy link
Copy Markdown
Contributor

@t-tani I have a possible alternative fix for this, could I ask you to do some testing of #35143 using the same method you have used here?

@t-tani
t-tani commented Nov 27, 2024 •
Copy link
Copy Markdown
Author

@jonkoops
Sorry for the delayed response. I'll test today to see if the suggested fix work.
From what I can see in the code, it appears that the changes you've proposed should address the root cause of the issue.

@t-tani
t-tani commented Nov 27, 2024
Copy link
Copy Markdown
Author

The root cause has been addressed in PR #35143 which provides a comprehensive fix for this issue. Therefore, I will proceed to close this PR.

Thank you for the feedback and support during the investigation.

@t-tani t-tani 8000 closed this Nov 27, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

RESTART_AUTHENTICATION_ERROR in Iphone devices (using safari and chrome browser)

6 participants

0