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

如何在Next.js中使用MSAL实现双应用注册登录功能

实现Next.js中MSAL双租户/双应用注册的登录功能

我在Azure上有两个应用注册:一个面向内部用户(关联指定租户),另一个面向外部用户(关联不同租户)。我的Next.js应用登录页用MSAL实现了SSO,现在需要两个登录按钮,分别对应这两个应用注册的认证流程。但目前PublicClientApplication只能在根布局里用单一客户端ID和租户ID初始化,且需要全局共享实例,请问该怎么实现这种双登录功能?

现有代码如下:

原layout.tsx

"use client";
import { MsalProvider } from "@azure/msal-react";
import { PublicClientApplication } from "@azure/msal-browser";
import { msalConfig } from "../../msalConfig";

const msalInstance = new PublicClientApplication(msalConfig);

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <MsalProvider instance={msalInstance}>
      <html lang="pt-br">
        <body>
          <div>{children}</div>
        </body>
      </html>
    </MsalProvider>
  );
}

原Page.tsx

"use client";

import { useMsal } from "@azure/msal-react";
import appRegistrationUrl from "./config/app-registration-url";
export default function LoginPage() {
  const { instance } = useMsal();

  const handleLoginMsalExternalUser = () => {
    instance
      .loginRedirect({
        prompt: "create",
        scopes: ["User.Read", appRegistrationUrl],
      })
      .catch((e) => {
        console.error(e);
      });
  };
  const handleLoginMsalInternalUser = () => {
    instance
      .loginRedirect({
        prompt: "create",
        scopes: ["User.Read", appRegistrationUrl],
      })
      .catch((e) => {
        console.error(e);
      });
  };
  return (
    <div>
      <button type="button" onClick={handleLoginMsalInternalUser}>
        <span>Internal User login</span>
      </button>

      <button type="button" onClick={handleLoginMsalExternalUser}>
        <span>External User login</span>
      </button>
    </div>
  );
}

解决方案

1. 配置两个MSAL实例的参数

首先在msalConfig.ts中定义两组独立配置,分别对应内部和外部应用注册:

// msalConfig.ts
export const msalInternalConfig = {
  auth: {
    clientId: "YOUR_INTERNAL_CLIENT_ID", // 内部应用注册的客户端ID
    authority: "https://login.microsoftonline.com/YOUR_INTERNAL_TENANT_ID", // 内部租户ID
    redirectUri: "/", // 根据应用实际配置调整
    postLogoutRedirectUri: "/",
  },
  cache: {
    cacheLocation: "sessionStorage",
    storeAuthStateInCookie: false,
    cacheKey: "msal_internal_cache", // 自定义缓存键,避免与外部实例缓存冲突
  },
};

export const msalExternalConfig = {
  auth: {
    clientId: "YOUR_EXTERNAL_CLIENT_ID", // 外部应用注册的客户端ID
    authority: "https://login.microsoftonline.com/YOUR_EXTERNAL_TENANT_ID", // 外部租户ID,多租户场景可用`common`/`organizations`
    redirectUri: "/",
    postLogoutRedirectUri: "/",
  },
  cache: {
    cacheLocation: "sessionStorage",
    storeAuthStateInCookie: false,
    cacheKey: "msal_external_cache", // 独立缓存键
  },
};

2. 创建自定义Context管理双MSAL实例

由于MsalProvider仅支持绑定单个实例,我们用React Context全局共享两个实例:

// app/providers/MsalMultiInstanceProvider.tsx
"use client";
import { createContext, useContext, ReactNode } from "react";
import { PublicClientApplication } from "@azure/msal-browser";
import { msalInternalConfig, msalExternalConfig } from "../../msalConfig";

// 初始化两个独立的MSAL实例
const internalMsalInstance = new PublicClientApplication(msalInternalConfig);
const externalMsalInstance = new PublicClientApplication(msalExternalConfig);

// 创建Context
const MsalMultiInstanceContext = createContext({
  internalInstance: internalMsalInstance,
  externalInstance: externalMsalInstance,
});

export function MsalMultiInstanceProvider({ children }: { children: ReactNode }) {
  return (
    <MsalMultiInstanceContext.Provider value={{ internalInstance, externalInstance }}>
      {children}
    </MsalMultiInstanceContext.Provider>
  );
}

// 自定义Hook,方便组件获取实例
export function useMsalMultiInstance() {
  return useContext(MsalMultiInstanceContext);
}

3. 修改根布局使用自定义Provider

替换原有的MsalProvider为我们的多实例Provider:

// app/layout.tsx
"use client";
import { MsalMultiInstanceProvider } from "./providers/MsalMultiInstanceProvider";

export default function RootLayout({
  children,
}: Readonly<{
  children: React.ReactNode;
}>) {
  return (
    <MsalMultiInstanceProvider>
      <html lang="pt-br">
        <body>
          <div>{children}</div>
        </body>
      </html>
    </MsalMultiInstanceProvider>
  );
}

4. 修改登录页调用对应实例

在登录页中获取两个实例,分别处理内部和外部用户的认证流程:

// app/login/page.tsx
"use client";
import { useMsalMultiInstance } from "../providers/MsalMultiInstanceProvider";

export default function LoginPage() {
  const { internalInstance, externalInstance } = useMsalMultiInstance();

  // 内部用户登录逻辑
  const handleLoginInternal = () => {
    internalInstance
      .loginRedirect({
        prompt: "select_account", // 推荐用该值,避免强制创建新账号
        scopes: ["User.Read", "YOUR_INTERNAL_API_SCOPE"], // 替换为内部应用的权限范围
      })
      .catch((e) => console.error("内部登录失败:", e));
  };

  // 外部用户登录逻辑
  const handleLoginExternal = () => {
    externalInstance
      .loginRedirect({
        prompt: "select_account",
        scopes: ["User.Read", "YOUR_EXTERNAL_API_SCOPE"], // 替换为外部应用的权限范围
      })
      .catch((e) => console.error("外部登录失败:", e));
  };

  return (
    <div style={{ padding: "2rem" }}>
      <button
        type="button"
        onClick={handleLoginInternal}
        style={{ marginRight: "1rem", padding: "0.5rem 1rem" }}
      >
        内部用户登录
      </button>
      <button
        type="button"
        onClick={handleLoginExternal}
        style={{ padding: "0.5rem 1rem" }}
      >
        外部用户登录
      </button>
    </div>
  );
}

关键注意事项

  • 缓存隔离:通过设置不同的cacheKey确保两个实例的认证缓存互不干扰,避免登录状态混乱。
  • 权限范围:每个登录请求的scopes必须对应各自应用注册配置的权限,禁止混用。
  • 租户适配:外部应用若支持所有Azure AD租户,可将authority设为https://login.microsoftonline.com/common或organizations。
  • 实例初始化:两个PublicClientApplication实例必须在客户端组件(带"use client"指令)中初始化,确保在浏览器环境运行。

内容的提问来源于stack exchange,提问作者Agner Souza Bezerra

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.14 05:04:59