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.
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.