共用Azure应用注册的React SPA遇MSAL多匹配ID Token缓存错误求助
问题背景
两个React(v18) SPA应用共用同一Azure AD应用注册,使用@azure/msal-browser: 2.32.2和@azure/msal-react: 1.5.2,单独打开应用功能正常,但同时使用时触发以下错误:
ClientAuthError: multiple_matching_tokens: The cache contains multiple tokens satisfying the requirements. Call AcquireToken again providing more requirements such as authority or account..
at t [as constructor] (/aircraft-ui/main.b9f4f91dd3e752cc.esm.js:2:328717)
at new t (/aircraft-ui/main.b9f4f91dd3e752cc.esm.js:2:340847)
at t.createMultipleMatchingTokensInCacheError (/aircraft-ui/main.b9f4f91dd3e752cc.esm.js:2:342380)
at e.readIdTokenFromCache (/aircraft-ui/main.b9f4f91dd3e752cc.esm.js:2:408459)
at /aircraft-ui/main.b9f4f91dd3e752cc.esm.js:2:401953
at Array.map ()
at e.getAllAccounts (/aircraft-ui/main.b9f4f91dd3e752cc.esm.js:2:401888)
at e.getAllAccounts (/aircraft-ui/main.b9f4f91dd3e752cc.esm.js:2:615681)
at lc (/aircraft-ui/main.b9f4f91dd3e752cc.esm.js:2:630671)
at Object.ja [as useReducer] (/aircraft-ui/main.b9f4f91dd3e752cc.esm.js:2:1967211)
核心原因
两个应用共享同一Client ID,MSAL默认基于Client ID和Authority生成缓存键,导致浏览器缓存中的token被两个应用共享。当其中一个应用尝试读取缓存中的IdToken或获取账户时,MSAL无法区分目标token所属的应用,因此抛出多匹配错误。
解决方案
1. 为每个应用配置独立的缓存键
修改MSAL实例的缓存配置,给每个应用设置唯一的cacheKey,让它们拥有独立的缓存空间:
// 应用1的MSAL配置 const msalConfigApp1 = { auth: { clientId: "你的共享Client ID", authority: "https://login.microsoftonline.com/你的租户ID", redirectUri: "应用1的重定向URI" }, cache: { cacheLocation: "sessionStorage", // 推荐用sessionStorage减少跨标签页干扰 cacheKey: "app1-unique-cache-key" // 每个应用设置不同的唯一值 } }; const msalInstanceApp1 = new PublicClientApplication(msalConfigApp1);
// 应用2的MSAL配置 const msalConfigApp2 = { auth: { clientId: "你的共享Client ID", authority: "https://login.microsoftonline.com/你的租户ID", redirectUri: "应用2的重定向URI" }, cache: { cacheLocation: "sessionStorage", cacheKey: "app2-unique-cache-key" // 与应用1不同的唯一值 } }; const msalInstanceApp2 = new PublicClientApplication(msalConfigApp2);
2. 明确指定请求的账户参数
在调用acquireTokenSilent或获取账户时,明确传入目标账户信息,避免MSAL匹配多个token:
// 获取指定账户 const targetAccount = msalInstance.getAccountByUsername("user@your-domain.com"); // 或者通过homeAccountId获取 // const targetAccount = msalInstance.getAccountByHomeId("用户的homeAccountId"); // 静默获取token时指定账户 const tokenRequest = { scopes: ["user.read"], account: targetAccount // 明确指定账户 }; try { const response = await msalInstance.acquireTokenSilent(tokenRequest); } catch (error) { // 处理静默获取失败,比如弹出登录窗口 await msalInstance.acquireTokenPopup(tokenRequest); }
3. 在msal-react组件中筛选目标账户
使用useMsal钩子时,主动筛选出当前应用对应的账户:
import { useMsal } from "@azure/msal-react"; function MyComponent() { const { accounts, instance } = useMsal(); // 根据用户名或homeAccountId筛选目标账户 const currentAccount = accounts.find(acc => acc.username === "expected-user@domain.com"); // 使用currentAccount进行后续操作 return <div>当前用户:{currentAccount?.username}</div>; }
可选:升级MSAL版本
旧版本MSAL可能存在缓存处理的兼容性问题,建议升级到@azure/msal-browser和@azure/msal-react的最新稳定版(如v2.x的最新版或v3.x系列),新版本对多应用共享Client ID的场景有更好的支持。
内容的提问来源于stack exchange,提问作者ChubbyDude

