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

Multiple instances?

Open jamesmeneghello opened this issue 3 years ago • 10 comments

Thanks for the great library, took me some time to stumble across it but looks to be exactly what I need.

There doesn't appear to be support for multiple instances of the OIDC client, where a single SPA may need to authenticate against multiple different identity servers at the same time - do you have any ideas for how this could be worked around?

Similar to how https://github.com/AxaGuilDEv/react-oidc manages this: multiple configurations can be applied and then the hook can refer to a configuration to pull out a specific client instance.

jamesmeneghello avatar Mar 23 '22 09:03 jamesmeneghello

Untested idea: You could add per identity server one auth provider, nested. Then by using https://github.com/authts/react-oidc-context/blob/fd24a701a16c93d051bf25a82ac1a85dffb4f7a0/src/AuthProvider.tsx#L35-L46 you must makes sure that the correct auth provider interacts when needed....

pamapa avatar Mar 23 '22 09:03 pamapa

Yeah, so far as I know there's no way to retrieve a parent if there are multiple nested providers of the same type - React overrides it and gives you the nearest provider.

jamesmeneghello avatar Mar 23 '22 13:03 jamesmeneghello

Well, got it working with the most terrible of PoC code, for anyone that's curious: basically, store the higher-level provider in a secondary context for later retrieval (in my case, the aptly-named StupidContext.

Top-level:


type WrapperProviderProps = {
    children: React.ReactElement;
};

function InternalContextSave(props: WrapperProviderProps) {
    const auth = useAuth();
    return (
        <StupidContext.Provider value={auth}>
            {props.children}
        </StupidContext.Provider>
    );
}

function WrapperProvider(props: WrapperProviderProps) {
    return (
        <AuthProvider
            {...{
                authority: `${getEnvironmentVariable(
                    'VITE_KEYCLOAK_URL',
                )}/realms/${getEnvironmentVariable(
                    'VITE_KEYCLOAK_SECOND_REALM',
                )}`,
                client_id: getEnvironmentVariable(
                    'VITE_KEYCLOAK_SECOND_CLIENT_ID',
                ),
                redirect_uri: 'http://localhost:3000/second',
                silent_redirect_uri: 'http://localhost:3000/second',
            }}
            skipSigninCallback={window.location.pathname !== '/second'}
            onSigninCallback={(user) => {
                window.history.replaceState(
                    {},
                    document.title,
                    window.location.pathname,
                );
            }}
            loadUserInfo
        >
            <InternalContextSave>{props.children}</InternalContextSave>
        </AuthProvider>
    );
}

ReactDOM.render(
    <React.StrictMode>
        <WrapperProvider>
            <AuthProvider
                {...{
                    authority: `${getEnvironmentVariable(
                        'VITE_KEYCLOAK_URL',
                    )}/realms/${getEnvironmentVariable('VITE_KEYCLOAK_REALM')}`,
                    client_id: getEnvironmentVariable(
                        'VITE_KEYCLOAK_CLIENT_ID',
                    ),
                    redirect_uri: 'http://localhost:3000/first',
                    silent_redirect_uri: 'http://localhost:3000/first',
                }}
                skipSigninCallback={window.location.pathname !== '/first'}
                onSigninCallback={(user) => {
                    window.history.replaceState(
                        {},
                        document.title,
                        window.location.pathname,
                    );
                }}
                loadUserInfo

                // automaticSilentRenew
            >
                <App />
            </AuthProvider>
        </WrapperProvider>
    </React.StrictMode>,
    document.getElementById('root'),
);

Less high-level:


    useEffect(() => {
        if (!isLoading && !isAuthenticated) {
            window.sessionStorage.setItem(
                'redirectTo',
                `${location.pathname}${location.search}`,
            );
            signinRedirect();
        }
    }, [isLoading, isAuthenticated]);

    const secondAuth = useStupid();
    useEffect(() => {
        if (!secondAuth?.isLoading && !secondAuth?.isAuthenticated) {
            secondAuth?.signinRedirect();
        }
    }, [secondAuth?.isLoading, secondAuth?.isAuthenticated]);

Thanks for the idea!

jamesmeneghello avatar Mar 23 '22 14:03 jamesmeneghello

Thanks for sharing the solution!

pamapa avatar Mar 24 '22 16:03 pamapa

I don't think this has a very satisfying solution yet; we can keep this open.

The real issue is that we don't expose a way to obtain a ref to the auth context without a child component using the hook. Ideally, something like this should be possible:

import React, { createContext, useCallback, useContext } from 'react'
import { AuthProvider } from 'react-oidc-context';

const multiAuthContext = createContext(new Map());

function useMultiAuth(authority) {
  const auth = useContext(multiAuthContext).get(authority);
  if (!auth) throw new Error(`No authority configuration for ${authority}`);
  return auth;
}

function MyComponent() {
  const auth = useMultiAuth("https://authority1");
  useEffect(() => {
    auth.signinRedirect();
  }, []);
}

function App() {
  const authoritiesRef = useRef(new Map());
  const registerAuthority = useCallback(auth => authorities.set(auth.settings.authority, auth));

  return (
    <multiAuthContext.Provider value={authoritiesRef.current}>
      <AuthProvider ref={registerAuthority} authority="https://authority1" client_id="clientId1" />
      <AuthProvider ref={registerAuthority} authority="https://authority2" client_id="clientId2" />
      <MyComponent />
    </multiAuthContext.Provider>
  );
}

ReactDOM.render(
  <React.StrictMode>
    <App />
  </React.StrictMode>,
  document.getElementById('root'),
);

kherock avatar Apr 04 '22 17:04 kherock

Bumping this thread: the possibility of changing authority conditionally dynamically would be very useful because for example keycloak has the notion of realms and it is a normal use case to change authority, otherwise user needs to save realm in localStorage or a cookie or sessionStorage.

Kaspar-Metsa avatar Aug 24 '22 05:08 Kaspar-Metsa

Bumping this thread: the possibility of changing authority conditionally dynamically would be very useful because for example keycloak has the notion of realms and it is a normal use case to change authority, otherwise user needs to save realm in localStorage or a cookie or sessionStorage.

A better example would be integration with third party apps using oauth such as jira, in rare cases you would need several oidc client instances for the same keycloak instance. I will make a poc myself and see if I get a better solution

satanshiro avatar Oct 06 '22 13:10 satanshiro

Nice @satanshiro looking forward!

KasparMetsa avatar Oct 06 '22 13:10 KasparMetsa

hi unfortunately I can't test locally, I would like to try and do the following change to AuthProvider.tsx: return ( context ? ( <context.Provider value={{ ...state, ...userManagerContext, removeUser, signoutRedirect, signoutPopup, }} > {children} </context.Provider>): (<AuthContext.Provider value={{ ...state, ...userManagerContext, removeUser, signoutRedirect, signoutPopup, }} > {children} </AuthContext.Provider>) ); meaning I would like to be able to manually inject different auth context and test so users would only need to recreate useAuth and authcontext with a different name and as such 2 contexts should be different instances. npm pack does not work locally and npm link gives me other peer dependencies error. image `npm pack

[email protected] prepack npm run build

[email protected] build node scripts/build.js && npm run build-types

[email protected] build-types tsc --emitDeclarationOnly && api-extractor run

api-extractor 7.32.0 - https://api-extractor.com/

Using configuration from ./api-extractor.json Analysis will use the bundled TypeScript version 4.8.4 Warning: You have changed the public API signature for this project. Please copy the file "temp/react-oidc-context.api.md" to "docs/react-oidc-context.api.md", or perform a local build (which does this automatically). See the Git repo documentation for more info.

API Extractor completed with warnings npm ERR! code 1 npm ERR! path /home/oren/development/react-oidc-context npm ERR! command failed npm ERR! command sh -c -- npm run build

npm ERR! A complete log of this run can be found in: npm ERR! /home/oren/.npm/_logs/2022-10-11T13_22_44_739Z-debug-0.log` in any case it is an important feature for when users stop using api keys for remote api access and want an auth flow instead. does anyone know what I am missing to run npm pack locally?

satanshiro avatar Oct 11 '22 13:10 satanshiro

Anyone have ideas on how to solve this issue for now? Really appreciate any input!

monominia avatar Jul 10 '23 23:07 monominia