React集成Microsoft登录:从服务端获取clientId始终为undefined排查
问题:React集成Microsoft登录时,从服务端获取clientId始终返回undefined
开发React应用集成Microsoft登录,尝试从服务端/api/config接口读取clientId,但clientId一直返回undefined。相关代码如下:
服务端代码(/server/server.ts)
app.get("/api/config/", async function (request: Request, response: Response) { const clientId = "abc-djfjgjg-gjjj-495" const config = { "clientId": clientId } response.status(200).send({ ...config }); });
客户端代码
client/src/App.tsx
import { loginRequest, protectedResources } from "./config/config"; const App = () => { const authRequest = { ...loginRequest, }; return ( <> <MsalAuthenticationTemplate interactionType={InteractionType.Redirect} authenticationRequest={authRequest} > <HeaderComponent /> <div className="bodycontainer"> <MainPage/> </div> <FooterComponent/> </MsalAuthenticationTemplate> </> ); } export default App;
client/src/config.tsx
let clientId : any ; const fetchData = async () => { const response = await fetch("/api/config/"); const data = await response.json(); clientId = data } //CALL FETCH DATA TO GET CONFIG DETAILS FROM SERVER fetchData() console.log("What is clientId" + clientId) // always returned as undefined export const msalConfig = { auth: { clientId: 'xxxx-xxxxx-xxxxx-xxxx', authority: 'https://login.microsoftonline.com/xxxxx-xxxxxx-xxx-xx', redirectUri: '/', postLogoutRedirectUri: '/', clientCapabilities: ['CP1'], }, cache: { cacheLocation: 'localStorage', storeAuthStateInCookie: false, }, system: { loggerOptions: { loggerCallback: (level : any, message : any, containsPii: any) => { if (containsPii) { return; } switch (level) { case LogLevel.Error: console.error(message); return; case LogLevel.Info: console.info(message); return; case LogLevel.Verbose: console.debug(message); return; case LogLevel.Warning: console.warn(message); return; } }, }, }, }; export const protectedResources = { api: { firstEndpoint: 'http://localhost:3000/', dashboardEndpoint: 'http://localhost:3000/', scopes: ['api://xxxx-xxxxxx-x-xxxx/access_via_approle_assignments'], }, }; export const loginRequest = { scopes: [...protectedResources.api.scopes], }; export const appRoles = { Railway: "Railway" }
index.tsx
import { PublicClientApplication } from "@azure/msal-browser"; import { MsalProvider } from "@azure/msal-react"; import { msalConfig } from "./config/config"; const root = ReactDOM.createRoot( document.getElementById('root') as HTMLElement ); const msalInstance = new PublicClientApplication(msalConfig); root.render( <React.StrictMode> <BrowserRouter> <MsalProvider instance={msalInstance}> <App /> </MsalProvider> </BrowserRouter> </React.StrictMode> );
问题原因分析
- 异步执行顺序错误:
fetchData是异步函数,调用后会立即执行后续的console.log,此时HTTP请求还未完成,clientId仍保持初始的undefined。 - msalConfig初始化时机错误:
msalConfig在模块加载阶段就已创建,此时fetch请求还未返回结果,即使后续clientId被赋值,msalConfig中的clientId还是硬编码的固定值,不会自动更新。 - 数据解构错误:服务端返回的是
{clientId: "abc-djfjgjg-gjjj-495"},但客户端直接把整个data对象赋值给clientId,正确的做法应该提取data.clientId字段。
解决方案
1. 修复数据赋值错误
先修改config.tsx中的fetchData函数,正确提取clientId:
const fetchData = async () => { const response = await fetch("/api/config/"); const data = await response.json(); clientId = data.clientId; // 改为提取clientId字段 }
2. 调整msal实例初始化时机
由于msalInstance需要在应用启动时就初始化,必须确保拿到clientId后再创建实例。将配置获取逻辑移到index.tsx中,用异步方式启动应用:
修改index.tsx:
import { PublicClientApplication, LogLevel } from "@azure/msal-browser"; import { MsalProvider } from "@azure/msal-react"; import React from 'react'; import ReactDOM from 'react-dom/client'; import { BrowserRouter } from 'react-router-dom'; import App from './App'; import { protectedResources, loginRequest } from './config/config'; const initApp = async () => { // 1. 获取服务端配置 const response = await fetch("/api/config/"); const configData = await response.json(); const clientId = configData.clientId; // 2. 创建msal配置 const msalConfig = { auth: { clientId: clientId, authority: 'https://login.microsoftonline.com/xxxxx-xxxxxx-xxx-xx', redirectUri: '/', postLogoutRedirectUri: '/', clientCapabilities: ['CP1'], }, cache: { cacheLocation: 'localStorage', storeAuthStateInCookie: false, }, system: { loggerOptions: { loggerCallback: (level: any, message: any, containsPii: any) => { if (containsPii) return; switch (level) { case LogLevel.Error: console.error(message); return; case LogLevel.Info: console.info(message); return; case LogLevel.Verbose: console.debug(message); return; case LogLevel.Warning: console.warn(message); return; } }, }, }, }; // 3. 创建msal实例并渲染应用 const msalInstance = new PublicClientApplication(msalConfig); const root = ReactDOM.createRoot(document.getElementById('root') as HTMLElement); root.render( <React.StrictMode> <BrowserRouter> <MsalProvider instance={msalInstance}> <App /> </MsalProvider> </BrowserRouter> </React.StrictMode> ); }; // 启动异步初始化流程 initApp();
3. 简化config.tsx
移除config.tsx中的fetch相关逻辑,改为导出基础配置项(供其他组件使用):
import { LogLevel } from "@azure/msal-browser"; export const protectedResources = { api: { firstEndpoint: 'http://localhost:3000/', dashboardEndpoint: 'http://localhost:3000/', scopes: ['api://xxxx-xxxxxx-x-xxxx/access_via_approle_assignments'], }, }; export const loginRequest = { scopes: [...protectedResources.api.scopes], }; export const appRoles = { Railway: "Railway" };
说明
- 异步初始化流程确保
msalInstance创建时已拿到最新的clientId,避免使用未初始化的值。 - 修复数据提取错误后,能正确获取服务端返回的
clientId字段。 - 将配置逻辑集中到入口文件,保证实例创建时机与数据获取顺序一致。
内容的提问来源于stack exchange,提问作者KGT
相关产品推荐
相关产品推荐

