reactjsazure-active-directorymsal-react

Exporting msalInstance from main entry file causes circular calls


I have a web app that uses @azure/msal-react on the front end for the user to log in with their Microsoft account. The app was working great. Then I wanted to figure out how to authenticate my calls to the backend. So after a ton of digging, I pretty much figured that out. Now after putting the final piece into place, my app started working goofy. After modifying some front-end code it would reload the whole page instead of doing a hot reload, (I am using Vite BTW), and then after that nothing on the page would work. I kept looking for errors in the console and finally started seeing some errors like this: [hmr] /src/components/App.tsx failed to apply HMR as it's within a circular import. Reloading page to reset the execution order. To debug and break the circular import, you can run vite --debug hmr to log the circular dependency path if a file change triggered it.

So I think I've narrowed it down to my main.tsx file. I am exporting the msalInstance like this: export const msalInstance = new PublicClientApplication(msalConfig); and then using that instance in my api calls get a fresh auth token.

Here is my entire main.tsx:

import React from 'react';
import ReactDOM from 'react-dom/client';
import { BrowserRouter as Router } from 'react-router-dom';

import App from './App';

// MSAL imports
import {
    PublicClientApplication,
    EventType,
    AuthenticationResult
} from '@azure/msal-browser';
import { msalConfig } from './authConfig';

// Bootstrap CSS
import 'bootstrap/dist/css/bootstrap.min.css';
// Bootstrap Bundle JS
import 'bootstrap/dist/js/bootstrap.bundle.min';
import './assets/fontawesome-pro-6.0.0-web/js/all.min';
import './assets/fontawesome-pro-6.0.0-web/scss/fontawesome.scss';
import 'react-confirm-alert/src/react-confirm-alert.css';
import 'react-toastify/dist/ReactToastify.css';
import 'react-float-menu/dist/react-float-menu.css';

import './assets/styles/App.css';

export const msalInstance = new PublicClientApplication(msalConfig);

msalInstance.initialize().then(() => {
    // Default to using the first account if no account is active on page load
    if (
        !msalInstance.getActiveAccount() &&
        msalInstance.getAllAccounts().length > 0
    ) {
        // Account selection logic is app dependent. Adjust as needed for different use cases.
        msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]);
    }

    // Optional - This will update account state if a user signs in from another tab or window
    msalInstance.enableAccountStorageEvents();

    msalInstance.addEventCallback((event) => {
        if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
            const payload = event.payload as AuthenticationResult;
            const account = payload.account;
            msalInstance.setActiveAccount(account);
        }
    });
    ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
        <React.StrictMode>
            <Router>
                <App pca={msalInstance} />
            </Router>
        </React.StrictMode>
    );
});

I did test ripping out the authentication to get the token before the api calls and the auth on the backend and the issue did appear to go away. I thought maybe I would move all the msalInstance code further down the chain to the App file. But the app didn't even want to load after I tried that. Any help would be much appreciated!

Update: I am also seeing this error which seems related: Warning: You are calling ReactDOMClient.createRoot() on a container that has already been passed to createRoot() before. Instead, call root.render() on the existing root instead if you want to update it.

Update2: Also adding the Axios instance where I am getting the token

import axios from 'axios';
import {msalInstance} from '../main';
import {msalConfig} from "../authConfig";

const baseURL = import.meta.env.VITE_BASE_URL;

const axiosInstance = axios.create({
    baseURL: baseURL + '/'
});


async function getToken(): Promise<string> {
    const currentAccount = msalInstance.getActiveAccount();
    // const tokenRequest = {
    //  account: instance.getActiveAccount(),
    //  scopes: [import.meta.env.VITE_QUOTES_API_SCOPE]
    // };
    const accessTokenRequest = {
        scopes: [import.meta.env.VITE_QUOTES_API_SCOPE],
        account: currentAccount,
    };

    if (currentAccount) {
        if (currentAccount.tenantId == msalConfig.auth.tenantId) {
            const roles = (currentAccount.idTokenClaims as { [key: string]: any }).roles;
            if (roles) {
                //  const intersection = Object.keys(appRoles).filter((role) => roles.includes(role));
                //  if (intersection.length > 0) {
                const accessTokenResponse = await msalInstance.acquireTokenSilent(accessTokenRequest);
                return `Bearer ${accessTokenResponse.accessToken}`;
                //  }
            }
        }
        return null;
    }
}


axiosInstance.interceptors.request.use(async (config) => {
    config.headers['request-startTime'] = new Date().getTime();

    // 07-28-24 - new token code, we can even check the url path if we need to
    // if (config.url.indexOf("/admin") !== 0) {
    // 8-2-24 - uncomment this next line back in after circular issue is revolved
    // config.headers.Authorization = await getToken();
    // }
    // return config;

    return config;
});

axiosInstance.interceptors.response.use((response) => {
    const currentTime = new Date().getTime();
    const startTime = response.config.headers['request-startTime'];
    response.headers['request-duration'] = currentTime - startTime;
    return response;
});

export default axiosInstance;

Update3: Here is a link directly from Microsoft on how to do this: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-react/docs/migration-guide.md#acquiring-an-access-token I have tried following that to no avail.


Solution

  • To resolve the circular dependency issue, you can refactor your code to avoid importing msalInstance directly from main.tsx. Instead, create a separate module to handle the MSAL instance and any related logic. This will help break the circular dependency.