页面刷新导致MSAL React实例初始化失败问题排查
我正在开发一个使用MSAL的React应用,流程为用户重定向到登录页面,认证成功后返回应用。HomePage.tsx加载时会触发第三方API调用,需使用MSAL生成的访问令牌,首次运行一切正常。
但登录成功后刷新页面时,调用msalInstance.acquireTokenSilent()会失败,报错如下:
uninitialized_public_client_application
You must call and await the initialize function before attempting to call any other MSAL API.
首次登录返回应用后,msalInstance已完成初始化,可正常获取访问令牌;但刷新页面后,msalInstance处于未初始化状态。
对比另一个同流程但运行正常的React应用,唯一差异是MSAL版本:
正常应用依赖版本:
"@azure/msal-browser": "^2.32.2", "@azure/msal-react": "^1.5.2",
当前新应用依赖版本:
"@azure/msal-browser": "^3.5.0", "@azure/msal-react": "^2.0.7",
想了解msal-react v2是否需要不同的处理逻辑?是否遗漏了关键步骤导致页面刷新后MSAL实例未初始化?
相关代码
main.tsx
const msalConfig: Configuration = { auth: { clientId: config.clientId, authority: config.authority } } const pca = new PublicClientApplication(msalConfig); const authRequest = { scopes: ["openid", "profile"] }; root.render( <MsalProvider instance={pca}> <MsalAuthenticationTemplate interactionType={InteractionType.Redirect} authenticationRequest={authRequest}> <App msalInstance={pca} msalConfig={config}/> </MsalAuthenticationTemplate> <UnauthenticatedTemplate> <p>No users are signed in!</p> </UnauthenticatedTemplate> </MsalProvider> )
App.tsx
function App(props: any) { const { instance, accounts, inProgress } = useMsal(); const [bookmarkedApps, setBookmarkedApps] = useState<UserApp[]>(); const getAccessToken = async () => { try{ const msalResponse = await props.msalInstance.acquireTokenSilent({ scopes: [props.msalConfig.scopes], account: accounts[0] }); return msalResponse.accessToken; } catch (error) { console.log(error!.errorCode); console.log(error!.errorMessage); } } const getBookmarkedApps = async () => { const resp = await fetch('/api/apps/bookmarked', { method: 'POST', headers: { 'Content-Type': 'application/json', 'Authorization': 'Bearer ' + await getAccessToken() }, body: JSON.stringify({ environmentConfigId: 1 }) }); if(resp.ok) { const data = await resp.json(); setBookmarkedApps(data); } } const router = createBrowserRouter([ { path: '/', element: <RootLayout />, children: [ { path: '/', element: <HomePage getAccessToken={getAccessToken} bookmarkedApps={bookmarkedApps}/> }, { path: '/apps', element: <Outlet />, children: [ { path: '/apps/manage', element: <ManageApps /> }, ] } ] }, ]); useEffect(() => { getBookmarkedApps(); }, []); return ( <RouterProvider router={router} /> ); } export default App;
package.json依赖
"dependencies": { "@azure/msal-browser": "^3.5.0", "@azure/msal-react": "^2.0.7", "@emotion/react": "^11.11.1", "@emotion/styled": "^11.11.0", "@mui/icons-material": "^5.14.18", "@mui/material": "^5.14.18", "react": "^18.2.0", "react-dom": "^18.2.0", "react-router": "^6.20.0", "react-router-dom": "^6.20.0" }
MSAL.js v3(对应msal-react v2)的核心变更之一是:PublicClientApplication实例必须显式调用并等待initialize()方法完成后,才能调用其他MSAL API,而v2版本中实例是自动初始化的,这是导致刷新后报错的根本原因。
具体修复步骤
修改main.tsx,添加异步初始化逻辑
创建MSAL实例后,需先调用await pca.initialize(),再渲染应用。由于React 18的root.render是同步操作,需用异步函数包裹:const msalConfig: Configuration = { auth: { clientId: config.clientId, authority: config.authority } } const pca = new PublicClientApplication(msalConfig); // 异步初始化MSAL实例后再渲染应用 async function renderApp() { await pca.initialize(); root.render( <MsalProvider instance={pca}> <MsalAuthenticationTemplate interactionType={InteractionType.Redirect} authenticationRequest={{ scopes: ["openid", "profile"] }}> <App msalConfig={config}/> </MsalAuthenticationTemplate> <UnauthenticatedTemplate> <p>No users are signed in!</p> </UnauthenticatedTemplate> </MsalProvider> ); } renderApp();优化App.tsx中MSAL实例的使用
无需通过props传递msalInstance,直接使用useMsal()返回的instance,符合msal-react的最佳实践:const getAccessToken = async () => { try{ // 使用useMsal返回的instance,替代props传递的实例 const msalResponse = await instance.acquireTokenSilent({ scopes: [props.msalConfig.scopes], account: accounts[0] }); return msalResponse.accessToken; } catch (error) { console.log(error!.errorCode); console.log(error!.errorMessage); } }避免在MSAL初始化过程中触发API调用
利用useMsal()返回的inProgress状态,仅当MSAL无正在进行的操作且已有登录账户时,再调用API:useEffect(() => { // 确保MSAL初始化完成且用户已登录 if (inProgress === InteractionStatus.None && accounts.length > 0) { getBookmarkedApps(); } }, [inProgress, accounts]);
原因说明
MSAL.js v3将初始化从同步改为异步,目的是提升页面加载性能和实例初始化的可靠性。如果跳过initialize()调用,实例会一直处于未初始化状态,页面刷新后所有依赖MSAL的操作都会触发报错。而v2版本中实例自动完成初始化,因此旧应用无此问题。
内容的提问来源于stack exchange,提问作者LNX

