oidc-client-ts icon indicating copy to clipboard operation
oidc-client-ts copied to clipboard

Token reappears after logout

Open rwb196884 opened this issue 10 months ago • 9 comments

After logging out oidc-client-ts thinks it's still logged in. I suspect that it is hiding data somewhere. The problem continues if I clear all site data (Firefox) but not in a new private window.

To log out I redirect to IdentityServer to log out there, then that passes back to my React app which does this

if (isServersideLogoutRedirect()) {
      setState(currentState => ({ ...currentState, isLoggingOut: true, user: null }));
      userManager.current
        .removeUser()
        .then(() => {
          userManager.current.revokeTokens();
        })
        .then(() => {
          userManager.current.clearStaleState();
        })
        .then(() => {
          setState({
            user: null,
            authenticationError: null,
            isLoggingIn: false,
            isLoggingOut: false
          });
        })
        .then(() => {
          navigate('/', { replace: true });
        });

However, if I then refresh the page then this thinks that I am still logged in. (It is a multi-tenant system and the React app runs on a subdomain for each tenant.)

  useEffect(() => {
    const doEffect = async () => {
      let s: string = appSettings?.rootTenantSubdomain ?? 'prestwood';
      let isAuthenticatedResult: boolean = false;
      try {
        s = getFirm();
        isAuthenticatedResult = await isAuthenticated();
      } catch {
        return;
      }
      // If the browser is on a subdomain other than that in the token the redirect
      // because we believe the token more than we believe the browser.
      if (
        isAuthenticatedResult &&
        s !== null &&
        s !== undefined &&
        s.toLowerCase() !== subdomain.toLowerCase() &&
        state.user !== null &&
        state.user.expired === false /*&& isAuthenticated()*/
      ) {
        console.error(`Browser is at subdomain ${subdomain} but token firm is ${s} user: ${JSON.stringify(state.user)}`);
        const newDomain = [...domain];
        newDomain[0] = s;
        window.location.replace(`${window.location.protocol}//${newDomain.join('.')}${window.location.pathname + window.location.search}`);
      }
    };

rwb196884 avatar Sep 15 '23 10:09 rwb196884

You will need to debug why, maybe a silent renew?

pamapa avatar Sep 15 '23 12:09 pamapa

But I've logged out, revoked tokens, and cleared state -- it shouldn't have secretly retained a token.

rwb196884 avatar Sep 15 '23 13:09 rwb196884

And it all works fine both in a private window and in other browsers. Therefore I think that it's secretly keeping data that it shouldn't be.

rwb196884 avatar Sep 15 '23 13:09 rwb196884

session cookie?

pamapa avatar Sep 15 '23 14:09 pamapa

I'm not using cookies. I'm using JWT.

This is still fucked.

I log out and on the callback do

  const handleLogout = () => {
    setAuthenticationState((currentState) => ({ ...currentState, isLoggingOut: true, user: null }));
    console.log('handleLogout');
    userManager.current
      .removeUser()
      .then(() => {
        userManager.current.revokeTokens(); // Call the token revocation endpoint (when settings are set).
      })
      .then(() => {
        userManager.current.clearStaleState();
      })
      .then(() => {
        setAuthenticationState({
          user: null,
          authenticationError: null,
          isLoggingIn: false,
          isLoggingOut: false
        });
      })
      .then(() => {
        navigate('/', { replace: true });
      });
  }

and then at / in useEffect the fucking userManager.current.getUser has reappeared.

rwb196884 avatar Feb 23 '24 11:02 rwb196884

Like @pamapa said it could be due to a silent renew.

You can clear/remove anything you wan't on the application side, but if you are not properly logged out from your IdentityServer (ie. if you still have a session running), then the next time you load your app, a silent renew can brings your tokens back.

So are you sure you are not doing a silent renew during your app launch ? And are you sure you are really logged out from your IdentityServer ?

Badisi avatar Feb 23 '24 16:02 Badisi

I'm experiencing the same issue. Using oidc-client-ts with react-oidc-context. I've already set the automaticSilentRenew to false, logout with signoutRedirect(), clear the sessionStorage. But the web still got the authorization code params automatically on the browser which makes the user stays logged in.

dszy579 avatar May 15 '24 04:05 dszy579

go to your IDP, kick the user out and see if your application behaves correctly (nothing should works siginSilent or silent_Renew). As long as your IDP still "know" you're not logout via IDP it always bring your back (you have token).

deanmaster avatar May 15 '24 04:05 deanmaster

This is what I'm doing at the moment:

  const logOut = () => {
    setAuthenticationState((currentState) => ({ ...currentState, isLoggingOut: true }));

    if (!authenticationState.user) {
      // Must be already logged out, or more likely oidc-client-ts is in a muddle.
      userManager.current
        .removeUser()
        .then(() => {
          userManager.current.revokeTokens(); // Call the token revocation endpoint (when settings are set).
        })
        .then(() => {
          userManager.current.clearStaleState();
        })
        .then(() => {
          setAuthenticationState({
            user: null,
            authenticationError: null,
            isLoggingIn: false,
            isLoggingOut: false
          });
        })
        .then(() => {
          navigate('/', { replace: true });
        });
      return;
    }

    const signoutRedirectArgs: SignoutRedirectArgs = {
      extraQueryParams: undefined,
      state: undefined,
      post_logout_redirect_uri: `${window.location.protocol}//${window.location.host}/${serversideLogoutRedirectUrl}`,
      id_token_hint: authenticationState.user!.id_token // https://github.com/IdentityServer/IdentityServer4/issues/1616
    };

    userManager.current.signoutRedirect(signoutRedirectArgs);
    // State is removed in the callback.
  };

and

...
    } else if (isServersideLogoutRedirect()) {
      setAuthenticationState((currentState) => ({ ...currentState, isLoggingOut: true, user: null }));
      userManager.current
        .removeUser()
        .then(() => {
          userManager.current.revokeTokens(); // Call the token revocation endpoint (when settings are set).
        })
        .then(() => {
          userManager.current.clearStaleState();
        })
        .then(() => {
          setAuthenticationState({
            user: null,
            authenticationError: null,
            isLoggingIn: false,
            isLoggingOut: false
          });
        })
        .then(() => {
          navigate('/', { replace: true });
        });
    } else {
...

rwb196884 avatar May 15 '24 08:05 rwb196884