react-oidc-context icon indicating copy to clipboard operation
react-oidc-context copied to clipboard

unclear how to sign out without hooks

Open justin-barca-at-camis opened this issue 3 years ago • 11 comments

Our project was using AppAuth-JS library before discovering this. It is much more succinct and sensible so thank you.

One of the challenges faced in using this library: we are using Apollo client to make GraphQL requests and we use a link to detect 401 errors. From the link, it's not clear how one could trigger a signout redirect. Thus, as a workaround, a boolean flag needsSignoutRedirect was created in our redux store which gets picked up by a React hook which performs the signout.

How can one halt all rendering while the page redirects though? If the network error reaches the query hook, the page will display an error message momentarily. Throwing an error in the link and catching it in the error boundary in order to halt the rendering cycles could prevent the hook from catching the needsSignoutRedirect flag.

justin-barca-at-camis avatar Apr 04 '22 15:04 justin-barca-at-camis

Could you state what functionality seems to be missing from this library more clearly? I'm having trouble understanding your problem, especially around

we use a link to detect 401 errors

kherock avatar Apr 04 '22 16:04 kherock

@kherock on 401, I need to ensure session was ended on the backend so I have to call auth.signinRedirect but I don't have access to hooks in the apollo link. Here's a reference for apollo link error handler: https://www.apollographql.com/docs/react/data/error-handling/#on-graphql-errors

justin-barca-at-camis avatar Apr 04 '22 16:04 justin-barca-at-camis

ah I see. I'm not familiar with Apollo Link but it sounds like you want to perform the sign out redirect from outside of the context of your React app?

Would creating a standalone UserManager instance from oidc-client-ts directly solve your problem?

kherock avatar Apr 04 '22 16:04 kherock

Hmm, thanks @kherock, I will try creating my own UserManager and passing it to the implementation prop. However, after looking into this further, I think this library might benefit from an "imperative handle" as described in this related SO issue: https://stackoverflow.com/questions/59232118/how-to-acess-react-context-from-apollo-set-context-http-link

justin-barca-at-camis avatar Apr 04 '22 16:04 justin-barca-at-camis

Just an FYI, implementation takes in a class instance, this library doesn't support passing an external usermanager instance.

What I meant in my suggestion was in response to

I have to call auth.signinRedirect but I don't have access to hooks in the apollo link

I may have misunderstood and you actually just meant that you're in a server context in Link. If you need to do OIDC client things (such as redirecting to the IDP) from outside of any React context on the client side, you should use oidc-client-ts directly in those contexts.

kherock avatar Apr 04 '22 16:04 kherock

I think this example is good argument for allowing external usermanager instances to be passed as a prop to the provider. @pamapa?

kherock avatar Apr 04 '22 17:04 kherock

In researching the issue of not being able to access context values from the Apollo link, I discovered this: https://stackoverflow.com/questions/59232118/how-to-acess-react-context-from-apollo-set-context-http-link . It sound like this library could use an imperative handle to the context in order to enable this functionality. I think it would be a better approach than passing UserManager instance because the user could get access to the whole api of the hook.

It's not very clear how to use the oidc-client-ts library in order to perform a signout redirect. Do I have to create another UserManager? Is it going to be compatible with this library's context UserManager e.g. if I sign in with react-oidc-context can I sign out with a newly constructed UserManager from oidc-client-ts?

justin-barca-at-camis avatar Apr 04 '22 17:04 justin-barca-at-camis

You would have to create another UserManager, but I think providing an easy way of obtaining a handle to the context without creating a child component is the better solution here. UserManager instances don't talk to each other, so some things would fall out of sync. This is kind of related to #292.

If you have time to open a PR adding an imperative handle to AuthProvider, I'd appreciate it!

kherock avatar Apr 04 '22 17:04 kherock

@kherock I was able to confirm instantiating another UserManager using the same config as the provider and calling it's signoutRedirect seemed to work as expected. I will see if I can dedicate some company bandwidth for a PR.

justin-barca-at-camis avatar Apr 04 '22 17:04 justin-barca-at-camis

@kherock do you think you could speak more to "some things would fall out of sync"? I'm wondering what the potential issues could be for JUST signoutRedirect. I read here in OIDC, if prompt is none, an id token hint is needed for refresh but I'm not sure if it's needed for signout and I found this document a bit confusing/vague.

justin-barca-at-camis avatar Apr 04 '22 21:04 justin-barca-at-camis

Actually, I take that back. Two user managers should be able to operate next to each other as long as they aren't racing with each other for certain global resources. I think the only instances where this may come up is when waiting for a popup window or silent iframe to respond.

kherock avatar Apr 04 '22 21:04 kherock

I added a separate issue for passing an UserManager into: #490

pamapa avatar Aug 31 '22 07:08 pamapa

For those still struggling with this I have this issue trying to create a method that run the keycloak's sign out in the axios response interceptor. Here's how I did it:

I've create a file called UserManagerClient.ts

import {
  UserManager,
  UserManagerSettings,
  WebStorageStateStore,
} from 'oidc-client-ts'

const userConfig: UserManagerSettings = {
  authority: 'authority_here',
  client_id: 'client_here',
  redirect_uri: 'http://localhost:3000'
}

const userManager = new UserManager(userConfig)

export default userManager

In my App.tsx

import { AuthProvider, hasAuthParams, useAuth } from 'react-oidc-context'
import UserManagerClient from './UserManagerClient'

const oidcConfig = {
  userManager:
    UserManagerClient,
  onSigninCallback: (): void => {
    window.history.replaceState({}, document.title, window.location.pathname)
  },
}

const App = () => (
  <AuthProvider {...oidcConfig}>
    <AppRouter/>
  </AuthProvider>
)

export default App

In my axios interceptor:

import UserManagerClient from './UserManagerClient'

axios.interceptors.response.use(
  (response) => {
    return response
  },
  async (error) => {
    if (error?.response?.status === 401) {
      const user = await UserManagerClient.getUser()
      UserManagerClient.signoutRedirect({
        post_logout_redirect_uri: window.location.href,
        id_token_hint: user?.id_token,
      })
    }
  }
)

Rafael-Ramblas avatar Jun 30 '23 00:06 Rafael-Ramblas