authts / react-oidc-context

Lightweight auth library based on oidc-client-ts for React single page applications (SPA). Support for hooks and higher-order components (HOC).
MIT License
711 stars 67 forks source link

addUserSignedOut event being fired when user is signed in #982

Open dannycarrera opened 1 year ago

dannycarrera commented 1 year ago

Hello! Thanks for your libraries!

I'm having some difficulty with getting Single Sign Out to work with a React app. At first I was experimenting with oidc-client-ts, and ran into an issue with infinite checkSession calls being made in quick succession when monitorSession was enabled. Changing checkSessionIntervalInSeconds had no impact. I thought maybe I was setting things up wrong. Even in simple sandbox apps, I was still getting this behaviour. I was about to file this issue in oidc-client-ts when I found this library.

Before creating that issue though, I was going through the examples in this library. When I enable monitorSessions with this library, the addUserSignedOut event is fired when a user is signed in. I see no calls being made to the OP. I also do not see events being fired when the user is actually signed out from the OP in another tab.

I'm not sure how to proceed. I don't think it's an issue with my config, as I'm copying the samples. IdentityServer4 is the OP being used.

Thanks for your help!

pamapa commented 1 year ago

Enabling logging of the underlying library (oidc-client-ts) should give you some insights whats going wrong. See https://authts.github.io/oidc-client-ts/#md:logging.

dannycarrera commented 1 year ago
// index.tsx
const oidcConfig: AuthProviderProps = {
  authority,
  client_id:clientId,
  redirect_uri,
  silent_redirect_uri,
  post_logout_redirect_uri,
  loadUserInfo: true,
  monitorSession: true,
  response_type: 'code',
  scope,
  onSigninCallback: () => {
    window.history.replaceState({}, document.title, '/');
  }
}
Log.setLogger(console)

const root = ReactDOM.createRoot(
  document.getElementById('root') as HTMLElement
)
root.render(
  <React.StrictMode>
   <AuthProvider {...oidcConfig}> 
   <App /> 
   </AuthProvider>, 
  </React.StrictMode>
)
// src/App.jsx
import { useEffect } from "react";
import { useAuth } from "react-oidc-context";

function App() {
    const auth = useAuth();

useEffect(() => {
  return auth.events.addUserLoaded(() => void console.log('user loaded'));
}, [auth.events])
useEffect(() => {
  return auth.events.addUserSignedIn(() => void console.log('user signed in'));
}, [auth.events])
useEffect(() => {
  return auth.events.addUserSessionChanged(() => void console.log('session changed'));
}, [auth.events])
useEffect(() => {
  return auth.events.addUserUnloaded(() => void console.log('user unloaded'));
}, [auth.events])
useEffect(() => {
  return auth.events.addUserSignedOut(() => void console.log('user signed out'));
}, [auth.events]);

    switch (auth.activeNavigator) {
        case "signinSilent":
            return <div>Signing you in...</div>;
        case "signoutRedirect":
            return <div>Signing you out...</div>;
    }

    if (auth.isLoading) {
        return <div>Loading...</div>;
    }

    if (auth.error) {
        return <div>Oops... {auth.error.message}</div>;
    }

    if (auth.isAuthenticated) {
        return (
        <div>
            Hello {auth.user?.profile.sub}{" "}
            <button onClick={() => void auth.signoutRedirect()}>Log out</button>
        </div>
        );
    }

    return <button onClick={() => void auth.signinRedirect()}>Log in</button>;
}

export default App;

This is the result with logging enabled. image

I'm not sure if it's the OP. IdentityServer4 is no longer supported. I will try again with keycloak to see if that resolves it at least.

pamapa commented 1 year ago

Until "user signed out" the log looks ok. After signed out the session monitor seems still running, but it should no longer.

I am not using SessionMonitor. But when i look at the code signoutRedirect calls removeUser, which calls _events.unload(). This send the event and in SessionMonitor this event should stop the monitoring in therory, but in your case not, the question is why.

dannycarrera commented 1 year ago

Just wanted to clarify that I have not clicked signoutRedirect(). All this happens after a signinRedirect().

Until "user signed out" the log looks ok

The ResponseValidator line with the login_required error seems to be where the issue starts and triggers the user signing out.

pamapa commented 1 year ago

The ResponseValidator line with the login_required error seems to be where the issue starts and triggers the user signing out.

In that case the "user signed out" comes most probably from the SessionMonitor itself. See https://github.com/authts/oidc-client-ts/blob/e78ff98b8085fb89c528c46cb6a3fe5ae26fb287/src/SessionMonitor.ts#L172-L178

When you increase the logging to debug you can see there more insights of why...

dannycarrera commented 1 year ago

I set Log.setLevel(Log.DEBUG) but the output is the same.

I think the issue is with IdentityServer4. When testing against Keycloak it seems to be working OK. image

The POST error occurs when I sign out from another tab (the Keycloak Account Management site). The user signed out event is emitted much later. Is this expected behaviour?

Seems a bit strange but also want to confirm that the user loaded event being continuously emitted is expected behaviour for monitorSession?

Otherwise I'll close this issue. Thanks for your help @pamapa 🙂

pamapa commented 1 year ago

Seems a bit strange but also want to confirm that the user loaded event being continuously emitted is expected behaviour for monitorSession?

I guess yes, but as said i am not using this feature.