Skip to content

Commit 84cf73e

Browse files
authored
Initialize MSAL before use to fix auth regression (#2685)
* frontend: Initialize MSAL before use, use stable instance, and guard async init (fixes uninitialized_public_client_application); show small loading state * frontend: inline catch for checkLoggedIn during auth init; keep initialize() gated with try/finally * Use different approach with MsalProvider in index
1 parent 5bc4fd7 commit 84cf73e

File tree

3 files changed

+75
-45
lines changed

3 files changed

+75
-45
lines changed

.github/chatmodes/fixer.chatmode.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ You MUST check task output readiness before debugging, testing, or declaring wor
2424
- Frontend: Vite provides HMR; changes in the frontend are picked up automatically without restarting the task.
2525
- Backend: Quart was started with --reload; Python changes trigger an automatic restart.
2626
- If watchers seem stuck or output stops updating, stop the tasks and run the "Development" task again.
27-
- To interact with a running application, use the Playwright MCP server
27+
- To interact with a running application, use the Playwright MCP server. If testing login, you will need to navigate to 'localhost' instead of '127.0.0.1' since that's the URL allowed by the Entra application.
2828

2929
## Committing the change
3030

app/frontend/src/index.tsx

Lines changed: 54 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,12 +4,15 @@ import { createHashRouter, RouterProvider } from "react-router-dom";
44
import { I18nextProvider } from "react-i18next";
55
import { HelmetProvider } from "react-helmet-async";
66
import { initializeIcons } from "@fluentui/react";
7+
import { MsalProvider } from "@azure/msal-react";
8+
import { AuthenticationResult, EventType, PublicClientApplication } from "@azure/msal-browser";
79

810
import "./index.css";
911

1012
import Chat from "./pages/chat/Chat";
1113
import LayoutWrapper from "./layoutWrapper";
1214
import i18next from "./i18n/config";
15+
import { msalConfig, useLogin } from "./authConfig";
1316

1417
initializeIcons();
1518

@@ -34,12 +37,54 @@ const router = createHashRouter([
3437
}
3538
]);
3639

37-
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
38-
<React.StrictMode>
39-
<I18nextProvider i18n={i18next}>
40-
<HelmetProvider>
41-
<RouterProvider router={router} />
42-
</HelmetProvider>
43-
</I18nextProvider>
44-
</React.StrictMode>
45-
);
40+
const root = ReactDOM.createRoot(document.getElementById("root") as HTMLElement);
41+
42+
// Bootstrap the app once; conditionally wrap with MsalProvider when login is enabled
43+
(async () => {
44+
let msalInstance: PublicClientApplication | undefined;
45+
46+
if (useLogin) {
47+
msalInstance = new PublicClientApplication(msalConfig);
48+
try {
49+
await msalInstance.initialize();
50+
51+
// Default active account to the first one if none is set
52+
if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
53+
msalInstance.setActiveAccount(msalInstance.getAllAccounts()[0]);
54+
}
55+
56+
// Keep active account in sync on login success
57+
msalInstance.addEventCallback(event => {
58+
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
59+
const result = event.payload as AuthenticationResult;
60+
if (result.account) {
61+
msalInstance!.setActiveAccount(result.account);
62+
}
63+
}
64+
});
65+
} catch (e) {
66+
// Non-fatal: render the app even if MSAL initialization fails
67+
// eslint-disable-next-line no-console
68+
console.error("MSAL initialize failed", e);
69+
msalInstance = undefined;
70+
}
71+
}
72+
73+
const appTree = (
74+
<React.StrictMode>
75+
<I18nextProvider i18n={i18next}>
76+
<HelmetProvider>
77+
{useLogin && msalInstance ? (
78+
<MsalProvider instance={msalInstance}>
79+
<RouterProvider router={router} />
80+
</MsalProvider>
81+
) : (
82+
<RouterProvider router={router} />
83+
)}
84+
</HelmetProvider>
85+
</I18nextProvider>
86+
</React.StrictMode>
87+
);
88+
89+
root.render(appTree);
90+
})();

app/frontend/src/layoutWrapper.tsx

Lines changed: 20 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,33 @@
1-
import { AccountInfo, EventType, PublicClientApplication } from "@azure/msal-browser";
2-
import { checkLoggedIn, msalConfig, useLogin } from "./authConfig";
3-
import { useEffect, useState } from "react";
4-
import { MsalProvider } from "@azure/msal-react";
1+
import { useEffect, useRef, useState } from "react";
2+
import { useMsal } from "@azure/msal-react";
3+
import { useLogin, checkLoggedIn } from "./authConfig";
54
import { LoginContext } from "./loginContext";
65
import Layout from "./pages/layout/Layout";
76

87
const LayoutWrapper = () => {
98
const [loggedIn, setLoggedIn] = useState(false);
109
if (useLogin) {
11-
var msalInstance = new PublicClientApplication(msalConfig);
12-
13-
// Default to using the first account if no account is active on page load
14-
if (!msalInstance.getActiveAccount() && msalInstance.getAllAccounts().length > 0) {
15-
// Account selection logic is app dependent. Adjust as needed for different use cases.
16-
msalInstance.setActiveAccount(msalInstance.getActiveAccount());
17-
}
18-
19-
// Listen for sign-in event and set active account
20-
msalInstance.addEventCallback(event => {
21-
if (event.eventType === EventType.LOGIN_SUCCESS && event.payload) {
22-
const account = event.payload as AccountInfo;
23-
msalInstance.setActiveAccount(account);
24-
}
25-
});
26-
10+
const { instance } = useMsal();
11+
// Keep track of the mounted state to avoid setting state in an unmounted component
12+
const mounted = useRef<boolean>(true);
2713
useEffect(() => {
28-
const fetchLoggedIn = async () => {
29-
setLoggedIn(await checkLoggedIn(msalInstance));
14+
mounted.current = true;
15+
checkLoggedIn(instance)
16+
.then(isLoggedIn => {
17+
if (mounted.current) setLoggedIn(isLoggedIn);
18+
})
19+
.catch(e => {
20+
console.error("checkLoggedIn failed", e);
21+
});
22+
return () => {
23+
mounted.current = false;
3024
};
31-
32-
fetchLoggedIn();
33-
}, []);
25+
}, [instance]);
3426

3527
return (
36-
<MsalProvider instance={msalInstance}>
37-
<LoginContext.Provider
38-
value={{
39-
loggedIn,
40-
setLoggedIn
41-
}}
42-
>
43-
<Layout />
44-
</LoginContext.Provider>
45-
</MsalProvider>
28+
<LoginContext.Provider value={{ loggedIn, setLoggedIn }}>
29+
<Layout />
30+
</LoginContext.Provider>
4631
);
4732
} else {
4833
return (

0 commit comments

Comments
 (0)