You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Next.js 14(App Router)集成Azure MSAL实现SSO时出现uninitialized_public_client_application错误求助

Next.js 14(App Router)集成Azure MSAL实现SSO时出现uninitialized_public_client_application错误求助

你好,我来帮你排查这个问题!你遇到的BrowserAuthError: uninitialized_public_client_application错误,核心原因是你的MSAL实例还没完成初始化就被调用了相关API。在新版本的@azure/msal-browser中,PublicClientApplication创建后必须显式调用并等待initialize()方法完成,才能使用loginRedirect、getAllAccounts这类API,而你的代码恰好缺少了这关键一步。

下面是具体的修复步骤:


第一步:重构MSAL实例的创建逻辑

修改你的Msal.ts,不要直接导出未初始化的实例,而是用一个单例函数来管理实例的创建和初始化,确保每次获取的都是已完成初始化的实例:

import {
  PublicClientApplication,
  Configuration,
  LogLevel,
} from "@azure/msal-browser";

const msalConfig: Configuration = {
  auth: {
    clientId: process.env.NEXT_PUBLIC_AZURE_AD_CLIENT_ID || "default-client-id",
    authority: `https://login.microsoftonline.com/${process.env.NEXT_PUBLIC_AZURE_AD_TENANT_ID}`,
    redirectUri: process.env.NEXT_PUBLIC_APP_REDIRECT_URL,
    postLogoutRedirectUri: "/",
  },
  cache: {
    cacheLocation: "sessionStorage",
    storeAuthStateInCookie: false,
  },
  system: {
    loggerOptions: {
      loggerCallback: (level, message, containsPii) => {
        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;
        }
      },
      logLevel: LogLevel.Info,
      piiLoggingEnabled: false,
    },
  },
};

// 用变量缓存实例,确保单例
let msalInstance: PublicClientApplication | null = null;

export const getMsalInstance = async () => {
  if (!msalInstance) {
    msalInstance = new PublicClientApplication(msalConfig);
    await msalInstance.initialize(); // 关键:等待初始化完成
  }
  return msalInstance;
};

第二步:更新Layout.tsx中的实例使用方式

把原来直接导入的msalInstance换成我们刚写的getMsalInstance函数,确保每次使用实例前都等待初始化完成:

"use client";
import React, { useEffect, useRef } from "react";
import "./globals.css";
import Header from "./components/Header";
import { usePathname, useRouter } from "next/navigation";
import { Box, Grid } from "@mui/material";
import { AppStore, makeStore, persistor } from "@/lib/store";
import { Provider } from "react-redux";
import { PersistGate } from "redux-persist/integration/react";
import { persistStore } from "redux-persist";
import Leftsidebar from "./components/Leftsidebar";
import { getMsalInstance } from "@/lib/Msal"; // 替换导入

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  const isAuthenticated = async () => {
    const msalInstance = await getMsalInstance(); // 先获取已初始化的实例
    const accounts = msalInstance.getAllAccounts();
    console.log("accounts", accounts);
    return accounts.length > 0;
  };

  const router = useRouter();
  const pathName = usePathname();

  useEffect(() => {
    const checkAuth = async () => {
      const auth = await isAuthenticated();
      // 权限校验逻辑保持不变
      if (!auth && pathName !== "/") {
        router.push("/");
      }
      if (auth && pathName === "/") {
        router.push("/dashboard");
      }
    };
    checkAuth();
  }, [pathName]); // 移除msalInstance依赖,因为getMsalInstance是单例

  const storeRef = useRef<AppStore>();
  if (!storeRef.current) {
    storeRef.current = makeStore();
  }
  return (
    <html lang="en">
      <body>
        <Provider store={storeRef.current}>
          <PersistGate loading={null} persistor={storeRef.current.__persistor}>
            <Box sx={{ display: "flex" }}>
              <Grid container sx={{ backgroundColor: "#F8F8F9" }}>
                <Grid item xs={true}>
                {children}
                </Grid>
              </Grid>
            </Box>
          </PersistGate>
        </Provider>
      </body>
    </html>
  );
}

第三步:修复登录函数的逻辑

同样,在handleLoginClick里先获取已初始化的实例,另外要注意:loginRedirect会触发页面跳转,跳转后的代码不会执行,所以账户信息的获取和路由跳转应该放在页面重新挂载后的状态检查中(比如Layout的useEffect),而不是在loginRedirect之后:

const handleLoginClick = async () => {
  try {
    const msalInstance = await getMsalInstance(); // 获取已初始化的实例
    const loginRequest = {
      scopes: ["openId", "profile", "User.Read"],
    };
    await msalInstance.loginRedirect(loginRequest);
    // 注意:这里的代码不会在跳转前执行,因为loginRedirect会直接跳转页面
    // 账户信息和状态更新请在Layout的useEffect中处理
  } catch (error) {
    console.error(error);
  }
};

额外说明

Next.js App Router的客户端组件中,不建议在模块顶层执行异步操作,所以用单例函数管理MSAL实例的初始化是最稳妥的方式,既保证了实例只被初始化一次,又能确保每次使用时都完成了初始化流程。

备注:内容来源于stack exchange,提问作者Seeker

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.17 12:14:52