Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Stuck with auth.isLoading = true with previous page action #1243

Open
ValGab opened this issue May 14, 2024 · 12 comments
Open

Stuck with auth.isLoading = true with previous page action #1243

ValGab opened this issue May 14, 2024 · 12 comments
Labels
bug Something isn't working help wanted Extra attention is needed

Comments

@ValGab
Copy link

ValGab commented May 14, 2024

Hello,

I have a page with several elements and a button that does this action.

<div 
  onClick={() => { auth.signinRedirect({ redirect_uri: window.location.origin + `/path` });  }}               
>

When I click this button, I am redirected to Keycloak to log in. But when I do "Back" with the browser, I'm stuck on my redirect display in my app because auth.isLoading=true and auth.activeNavigator="signinRedirect". I have experienced this problem with Firefox and Edge.

Here is my App.tsx file

const App: React.FC = () => {
  const auth = useAuth();

  switch (auth.activeNavigator) {
    case "signinSilent":
      return (
        <main className="redirect">
          <div>
            <p>Connexion en cours...</p>
            <Loader className="contained" />
          </div>
        </main>
      );
    case "signoutRedirect":
      return (
        <main className="redirect">
          <div>
            <p>Déconnexion en cours...</p>
            <Loader className="contained" />
          </div>
        </main>
      );
  }

  if (auth.isLoading) {
    return (
      <main className="redirect">
        <div>
          <p>Redirection en cours...</p>
          <Loader className="contained" />
        </div>
      </main>
    );
  }

  if (auth.error) {
    return (
      <main className="redirect">
        <div>
          <p>Vous n'êtes plus connecté(e)</p>
          <button
            className="action-btn"
            onClick={() => {
              auth.signinRedirect({ redirect_uri: window.location.href });
            }}
          >
            Se connecter
          </button>
        </div>
      </main>
    );
  }

There is a way to fix it and display my initial page with my button ?

Config :

  • React v18
  • Keycloak
@racacax
Copy link

racacax commented Jul 4, 2024

Hello,
I'm encoutering the same issue right now. I found a workaround by using the performance metrics from the browser to detect a "Back" navigation action.
It's just a dirty patch. The prefered behavior would be to have the "isLoading" state set to false when this happens.

useEffect(() => {
    /* react-oidc is stuck in isLoading mode when we trigger login process so we need to reload the Login page if user press the back button */
    const reloadIfPrevious = () => {
      const perfEntry = performance.getEntriesByType('navigation')[0] as PerformanceNavigationTiming
      if (perfEntry.type === 'back_forward') {
        location.reload()
      }
    }
    window.addEventListener('pageshow', reloadIfPrevious)
    return () => {
      window.removeEventListener('pageshow', reloadIfPrevious)
    }
  })

@pamapa pamapa added the bug Something isn't working label Jul 9, 2024
@pamapa
Copy link
Member

pamapa commented Jul 9, 2024

I guess the problem comes from here:

dispatch({
type: "NAVIGATOR_INIT",
method: key,
});
try {
return await userManager[key](args);
} catch (error) {
dispatch({ type: "ERROR", error: error as Error });
return null;
} finally {
dispatch({ type: "NAVIGATOR_CLOSE" });
}

When you click back on your IdP login page, you will not hit the catch or finally case. Thus NAVIGATOR_INIT is still pending. And as the page was previously loaded it does not reload again...

PS: The isLoading property is there to prevent other actions than the ones required for the sign-in process.

@pamapa
Copy link
Member

pamapa commented Jul 9, 2024

Not able to reproduce here.

I tried to reproduce with the IdP Entra ID. When I clicked on the browser back button on the IdP login page my application reloads. Thus i am not stuck with isLoading. I was using Firefox.

Also notice I have setup due to other reasons a no-caching on my index.html page via response header CacheControl setup in back-end...

@racacax
Copy link

racacax commented Jul 16, 2024

Indeed, with CacheControl set to no-cache, my app reloads no matter what. This is another workaround.

@AnzhelikaKotko
Copy link

AnzhelikaKotko commented Jul 18, 2024

Hello!
Please tell me if there are any updates on this issue? I ran into the same problem for several react+keycloak/auth0 applications. For our applications, this is only reproduced on browsers on iOS devices.

@pamapa
Copy link
Member

pamapa commented Jul 18, 2024

Please tell me if there are any updates on this issue? I ran into the same problem for several react+keycloak/auth0 applications. For our applications, this is only reproduced on browsers on iOS devices.

I am not planing to fix this. Also i have not an idea of how i could tackle this nicely without adding hack code... Have you tried the CacheControl thing?

@pamapa pamapa added the help wanted Extra attention is needed label Jul 18, 2024
@AnzhelikaKotko
Copy link

AnzhelikaKotko commented Jul 18, 2024

Hi, @pamapa ! Thank you for your quick response. I'm going to try this. Is it a correct value? <meta http-equiv="cache-control" content="no-cache" />

UPD: just tried to do it, this does not help

@pamapa
Copy link
Member

pamapa commented Jul 18, 2024

I guess the problem comes from here:

dispatch({
type: "NAVIGATOR_INIT",
method: key,
});
try {
return await userManager[key](args);
} catch (error) {
dispatch({ type: "ERROR", error: error as Error });
return null;
} finally {
dispatch({ type: "NAVIGATOR_CLOSE" });
}

When you click back on your IdP login page, you will not hit the catch or finally case. Thus NAVIGATOR_INIT is still pending. And as the page was previously loaded it does not reload again...

PS: The isLoading property is there to prevent other actions than the ones required for the sign-in process.

On the other hand the following code should reset the state on a re-render:

void (async (): Promise<void> => {
// sign-in
let user: User | void | null = null;
try {
// check if returning back from authority server
if (hasAuthParams() && !skipSigninCallback) {
user = await userManager.signinCallback();
onSigninCallback && await onSigninCallback(user);
}
user = !user ? await userManager.getUser() : user;
dispatch({ type: "INITIALISED", user });
} catch (error) {
dispatch({ type: "ERROR", error: signinError(error) });
}
// sign-out
try {
if (matchSignoutCallback && matchSignoutCallback(userManager.settings)) {
await userManager.signoutCallback();
onSignoutCallback && (await onSignoutCallback());
}
} catch (error) {
dispatch({ type: "ERROR", error: signoutError(error) });
}
})();

@AnzhelikaKotko Why is Line 241 dispatch({ type: "INITIALISED", user }); not executed when you click back on the IOS device? E.g. within Windows Chrome it does.

A complete other approach:
I just came aware of the setting redirectMethod, which is by default "assign" changing that to "replace" might prevent the back button on the IdP page. See https://authts.github.io/oidc-client-ts/classes/UserManagerSettingsStore.html#redirectMethod

@AnzhelikaKotko
Copy link

AnzhelikaKotko commented Jul 22, 2024

Hello @pamapa !

Sorry for the long response. I've tried to use redirectMethod with replace, but it's not convenient for my apps, the flow is changed for all browsers in this case.

What you think about this workaround? #1243 (comment)

Thank you!

UPDATED
#1243 (comment) this workaround does not work for ios:(

@AnzhelikaKotko
Copy link

AnzhelikaKotko commented Jul 24, 2024

Hello!

I found a solution for IOS browsers. I'll leave it here just in case someone catches the same

  useEffect(() => {
    const handlePageShow = (event: { persisted: boolean }) => {
      if (event.persisted && isIOS && auth.isLoading) {
        window.location.reload();
      }
    };

    window.addEventListener('pageshow', handlePageShow);

    return () => {
      window.removeEventListener('pageshow', handlePageShow);
    };
  }, []);

and some useful information could be found here https://web.dev/articles/bfcache

Also want to add, that in my case it's not a bug of react-oidc-contex but specific caching behaviour of ios browsers

@brt0555
Copy link

brt0555 commented Aug 14, 2024

It seems to be bigger issue, because for me it also does not work on Windows - Firefox & Edge. So it seems to be library issue not browsers.

Steps to reproduce:

  1. Go to app
  2. Click log in
  3. On login screen click back button in browser
  4. App is showing Loading Auth....
  5. App stuck with that state

That's my code (from official docs example):

export const AuthWrapper: React.FC<Props> = ({ children }) => {
  const auth = useAuth();
  const [hasTriedSignin, setHasTriedSignin] = useState(false);


  // automatically sign-in
  useEffect(() => {
    if (
      !hasAuthParams() &&
      !auth.isAuthenticated &&
      !auth.activeNavigator &&
      !auth.isLoading &&
      !hasTriedSignin
    ) {
      auth.signinRedirect();
      setHasTriedSignin(true);
    }
  }, [auth, hasTriedSignin]);

  if (auth.isLoading)
    return <>Loading Auth.... {JSON.stringify(auth, null, 2)}</>;

  if (auth && auth.isAuthenticated) return children;

  return (
    <p>You are not authenticated...</p>
  );
};

Properties of auth on which browser stuck after going back:
"isLoading": true, "isAuthenticated": false, "user": null, "activeNavigator": "signinRedirect",

Firefox Version: 129.0 (64 bits)
Edge Version: Version 127.0.2651.86 (Official build) (64-bit)
Microsoft Edge updates are managed by your organization.

@zach-betz-hln
Copy link

As mentioned here, I can only reproduce this when removing the Cache-Control: no-cache response header where our React index.html file is served.

I know various project setups exist. But for reference, we bundle our React apps with Vite, then serve the static assets with Express. Then we set the Cache-Control: no-cache response header in Express when serving the index.html file.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working help wanted Extra attention is needed
Projects
None yet
Development

No branches or pull requests

6 participants