AxaFrance / oidc-client

Light, Secure, Pure Javascript OIDC (Open ID Connect) Client. We provide also a REACT wrapper (compatible NextJS, etc.).
MIT License
570 stars 152 forks source link

Potential Issue with Local Storage During v3 to v7.x Migration #1347

Closed attiqeurrehman closed 2 months ago

attiqeurrehman commented 2 months ago

Issue and Steps to Reproduce

During our migration from v3 to v7.x, I carefully followed the provided migration guides. However, I'm having issues about local storage. While I assume the access token storage mechanism remains unchanged, I haven't been able to locate any entries for access tokens in local storage.

For your reference, our current configuration is:

export const configurationIdentityServer = {
    client_id: 'interactive.public.short',
    redirect_uri: window.location.origin + '/callback',
    silent_redirect_uri: window.location.origin + '/silent-callback',
    // silent_login_uri: window.location.origin + '/authentication/silent-login',
    scope: 'openid email profile role offline_access',
    authority: 'http://localhost:8080',
    // authority_time_cache_wellknowurl_in_second: 60* 60,
    refresh_time_before_tokens_expiration_in_second: 40,
    service_worker_relative_url: '/OidcServiceWorker.js',
    service_worker_only: false,
    storage: localStorage,
    // silent_login_timeout: 3333000
    // monitor_session: true,
    extras: { youhou_demo: 'youhou' },
    token_renew_mode: TokenRenewMode.access_token_invalid,
    token_automatic_renew_mode: TokenAutomaticRenewMode.AutomaticBeforeTokenExpiration,
    demonstrating_proof_of_possession: false,
};

In earlier version in order to get the access token to be used in axios we had following logic:

const setAuthToken = (axiosConfig) => {
  let oidcStorage = JSON.parse(
    sessionStorage.getItem(IDENTITY_SERVER_TOKEN_STORAGE_KEY)
  );
  if (oidcStorage) {
    axiosConfig.headers.Authorization = "Bearer ".concat(
      oidcStorage.access_token
    );
  }
  return axiosConfig;
};

axiosInstance.interceptors.request.use(setAuthToken);

Versions

7.22.0

Screenshots

Screenshot 2024-04-16 085329

Expected

Able to get the access token from local storage or session storage without the use of a hook.

Actual

Not able to get the access token from local storage or session storage with the use of hook

Additional Details

guillaume-chervet commented 2 months ago

hi @attiqeurrehman, thank you for your issue. Actually your configuration use The Service Worker Mode.

You should set this configuration as bellow and remove your registered service worker in Debug Chrome tab mode manually.

export const configurationIdentityServer = {
    client_id: 'interactive.public.short',
    redirect_uri: window.location.origin + '/callback',
    silent_redirect_uri: window.location.origin + '/silent-callback',
    // silent_login_uri: window.location.origin + '/authentication/silent-login',
    scope: 'openid email profile role offline_access',
    authority: 'http://localhost:8080',
    // authority_time_cache_wellknowurl_in_second: 60* 60,
    refresh_time_before_tokens_expiration_in_second: 40,
    storage: localStorage,
};

If you want to get access token outside of a react context, you can do:

import {OidcClient} from "@axa-fr/oidc-client";

const oidcClient = OidcClient.get("default")
const tokens = await oidcClient.getValidTokenAsync();
console.log(tokens);
attiqeurrehman commented 2 months ago

Thanks for such a quick response. I will try it tomorrow and update you on it.

attiqeurrehman commented 2 months ago

@guillaume-chervet I kept the session storage and changed the setAuthTokenmethod get the token with your recommendations but I am getting the following error:

OIDC library does seem initialized. Please checkout that you are using OIDC hook inside a <OidcProvider configurationName="default"></OidcProvider> component. Error: OIDC library does seem initialized. Please checkout that you are using OIDC hook inside a component. at F.get (http://localhost:3000/static/js/bundle.js:345553:67) at K.get (http://localhost:3000/static/js/bundle.js:345682:20) at setAuthToken (http://localhost:3000/static/js/bundle.js:32414:82) at async Axios.request (http://localhost:3000/static/js/bundle.js:370665:14)

guillaume-chervet commented 2 months ago

Hi @attiqeurrehman the method i give you upper should be called after the initialisation of the oidc provider.

attiqeurrehman commented 2 months ago

In fact I did called it after the initialisation of the provider inside the DisplayUserInfo.

guillaume-chervet commented 2 months ago

Hi @attiqeurrehman , do youvhave more information in order to help you. A sample of code for example.

attiqeurrehman commented 2 months ago

Hi @guillaume-chervet,

I have tried using the Secure Profile component, in the demos and sorry for the long post ahead. Here you go:

const configurationIdentityServer = {
  client_id: 'interactive.public.short',
  redirect_uri: window.location.origin + '/callback',
  silent_redirect_uri: window.location.origin + '/silent-callback',
  // silent_login_uri: window.location.origin + '/authentication/silent-login',
  scope: IDENTITY_SERVER_SCOPE,
  authority: 'http://localhost:8080',
  // authority_time_cache_wellknowurl_in_second: 60* 60,
  refresh_time_before_tokens_expiration_in_second: 40,
  service_worker_relative_url: '/OidcServiceWorker.js',
  service_worker_only: false,
  storage: localStorage,
  // silent_login_timeout: 3333000
  // monitor_session: true,
  token_renew_mode: TokenRenewMode.access_token_invalid,
  token_automatic_renew_mode: TokenAutomaticRenewMode.AutomaticBeforeTokenExpiration,
  demonstrating_proof_of_possession: false,
  //
  preload_user_info: true
};

export default function App() {
    const [isSessionLost, setIsSessionLost] = useState(false);

    const onEvent = (configurationName, eventName, data) => {
        console.log(`oidc:${configurationName}:${eventName}`, data);
    };

    const onSessionLost = () => {
        setIsSessionLost(true);
    };

    return (
        <ThemeProvider className="App">
            <OidcProvider
                configurationName="default"
                configuration={configurationIdentityServer}
                loadingComponent={ConfigurationLoading}
                authenticatingErrorComponent={AuthenticatingError}
                authenticatingComponent={Authenticating}
                serviceWorkerNotSupportedComponent={ServiceWorkerNotSupported}
                callbackSuccessComponent={CallBackSuccess}
                onSessionLost={onSessionLost}
                onEvent={onEvent}
            >
                { isSessionLost && <SessionLost/>}
                <SecureProfile />
            </OidcProvider>
        </ThemeProvider>
    );
}

and DisplayUserInfocomponent is calling a custom hook:

export default function useUserProfile() {
  const {oidcUser} = useOidcUser();
  return oidcUser;
}

// A custom hook that filters and reduces the user's view claims to only include those where the user has permission to read.
export default function useViewClaimsCanRead() {
  // Get the user's profile.
  const userProfile = useUserProfile();

  // Filter the profile to only include claims where the user has permission to read,
  // and reduce them into an object with each view and its corresponding permissions.
  // Return the filtered and reduced view claims.
  return Object.entries(userProfile)
    .filter(([key]) => key.endsWith(":CanRead") && userProfile[key] === "True")
    .reduce((result, [key]) => {
      // Extract the view name from the claim key.
      const view = key.slice(0, key.indexOf(":CanRead"));
      // Update the result object to include the view and its corresponding permission.
      return {
        ...result,
        [view]: true,
      };
    }, {});
}

const DisplayUserInfo = () => {

    const { oidcUser, oidcUserLoadingState, reloadOidcUser } = useOidcUser();
    console.log('DisplayUserInfo:oidcUser', oidcUser);

    const viewClaims = useViewClaimsCanRead();

    // removed for clarity
};

and I get the following errors:

oidc:default:tryKeepExistingSessionAsync_begin {}
App.js:32 oidc:default:tryKeepExistingSessionAsync_end {success: true, message: 'tokens inside ServiceWorker are valid'}
Profile.js:11 DisplayUserInfo:oidcUser null
useUserProfile.js:9 oidcUser null
useUserProfile.js:10 oidcUserLoadingState Unauthenticated
useUserProfile.js:11 reloadOidcUser () => {
    y(l + " ");
  }
Profile.js:11 DisplayUserInfo:oidcUser null
useUserProfile.js:9 oidcUser null
useUserProfile.js:10 oidcUserLoadingState Unauthenticated
useUserProfile.js:11 reloadOidcUser () => {
    y(l + " ");
  }
useViewClaimsCanRead.js:12  Uncaught TypeError: Cannot convert undefined or null to object
    at Function.entries (<anonymous>)
    at useViewClaimsCanRead (useViewClaimsCanRead.js:12:1)
    at DisplayUserInfo (Profile.js:13:1)
    at renderWithHooks (react-dom.development.js:14128:1)
    at mountIndeterminateComponent (react-dom.development.js:17417:1)
    at beginWork (react-dom.development.js:18713:1)
    at HTMLUnknownElement.callCallback (react-dom.development.js:3724:1)
    at Object.invokeGuardedCallbackDev (react-dom.development.js:3768:1)
    at invokeGuardedCallback (react-dom.development.js:3825:1)
    at beginWork$1 (react-dom.development.js:23694:1)

but if i remove the const viewClaims = useViewClaimsCanRead(); everything starts to work normally. Also, after thev7.22.4-alpha.1423 it works after login but when i reload it stops working.

attiqeurrehman commented 2 months ago

@guillaume-chervet any insight on it

guillaume-chervet commented 2 months ago

Hi @attiqeurrehman , very sorry for the delay. I am in holiday.

Did you tried lastest version?

When i look your error i think it should be fixed by the lastest version.

I will try to take more time to check your problem tomorrow.

attiqeurrehman commented 2 months ago

No problem. Enjoy your vacation!

I will try out the latest version and update you how it went.

attiqeurrehman commented 2 months ago

@guillaume-chervet The latest version (7.22.4) appears to be functioning correctly. I'll conduct further testing to confirm.

attiqeurrehman commented 2 months ago

@guillaume-chervet it worked like a charm.