在MSAL反应重定向到AZURE AD B2C之后,请在NextJS应用程序中预防错误页面的闪光灯。

发布于 2025-02-13 22:41:40 字数 5046 浏览 0 评论 0 原文

上下文&可重复的方案

我正在使用这些库和工具的组合:

我正在使用 auth code + pkce 重定向流,因此这是用户的流程:

  1. 他们降落在/,主页

  2. 他们单击/me 路由器链接

  3. 他们转到Azure B2C登录,因为所述页面具有此逻辑:

     < msalauthenticationTemplate
      InteractionType = {InteractionType.redirect}
      AuthenticationRequest = {loginRequest}>
     

    其中 loginrequest.state 设置为 router.aspath (“预期”页面:/me

    请注意,该页面也包裹在 a &lt; nossr&gt; 基于stack溢出的组件。<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<' /a>

  4. 用户在Azure B2C上登录,在/(root)上重定向回我的应用程序

  5. :现在用户简要介绍/(主页)页面

  6. 在很短的时间后,用户将发送到/me ,其中将其签名为< /p>

MSAL文档中签名的地方似乎在OIDC或THE的 State 属性上没有太多内容重定向行为,我在

简而言之:问题

如何确保我的nextJS应用程序中的msal反应将用户立即发送到启动上的“预期”页面,而不会简短地显示root Page的身份服务器重定向到位置?

相关的额外信息

这是我的自定义 _app.js 组件,这似乎是相关的,因为使重定向到预期的页面

export default function MyApp({ Component, pageProps }) {
  return (
    <MsalProvider instance={msalInstance}>
      <PageHeader></PageHeader>
      <Component {...pageProps} />
    </MsalProvider>
  );
}

ps。为了帮助人们在线搜索的人们找到以下问题:该行为是由 navigatetoginrequesturl触发的:true (是默认值)。将其设置为 false 明确完全禁用将用户完全发送到预期的页面。

使用中间件的尝试解决方案

我根据 app_initializer 在Angular中工作的方式尝试 ,使用中间件在某个时候这样:

// From another file:
// export const msalInstance = new PublicClientApplication(msalConfig);

export async function middleware(_request) {
    const targetUrlAfterLoginRedirect = await msalInstance.handleRedirectPromise()
        .then((result) => {
            if (!!result && !!result.state) {
                return result.state;
            }
            return null;
        });

    console.log('Found intended target before login flow: ', targetUrlAfterLoginRedirect);

    // TODO: Send user to the intended page with router.
}

但是,这会在服务器的控制台上登录:

在登录流之前找到了预期的目标:null

因此中间件似乎还为时过早, msal-reactct 无法应对?耻辱,因为中间件将是完美的,可以为目标页面提供尽可能多的SSR。

这不是更改B2C方面重定向URL的选择,因为我将不断地向需要此行为的应用添加新路由。

请注意,我还尝试使用中间件来嗅出状态,但是由于中间件在节点上运行,因此无法访问哈希片段。

动画gif显示闪烁的主页

这是一个动画gif,显示/home 页面在/me 之前已正确打开。警告,gif是一个浮躁的杂草,所以在扰流板标签中:

“从浏览器,问题的步骤捕获的屏幕截图”

从浏览器中,问题“> 使用自定义 navigationclient 尝试解决方案

的 添加自定义 navigationclient 以更紧密地模仿Microsoft存储库中的NextJS示例,例如:

import { NavigationClient } from "@azure/msal-browser";

// See: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-react/docs/performance.md#how-to-configure-azuremsal-react-to-use-your-routers-navigate-function-for-client-side-navigation
export class CustomNavigationClient extends NavigationClient {
  constructor(router) {
    super();
    this.router = router;
  }

  async navigateInternal(url, options) {
    console.log('
              

Context & Reproducible Scenario

I'm using the combination of these libraries and tools:

I'm using the Auth Code + PKCE redirect flow so this is the flow for users:

  1. They land on /, the home page

  2. They click a /me router link

  3. They go to Azure B2C to log in because said page has this logic:

    <MsalAuthenticationTemplate
      interactionType={InteractionType.Redirect}
      authenticationRequest={loginRequest}>
    

    where loginRequest.state is set to router.asPath (the "intended" page: /me)

    Note that the page is also wrapped in a <NoSsr> component based off Stack Overflow.

  4. User logs in on Azure B2C, gets redirected back to my app at / (the root)

  5. ⛔ Problem: the user now briefly sees the / (home) page

  6. After a very brief moment, the user gets sent to /me where they are signed in

The MSAL docs don't seem to have much on the state property from OIDC or this redirect behavior, and I can't find much about this in the MSAL sample for NextJS either.

In short: the issue

How do I make sure MSAL-React in my NextJS application send users to the "intended" page immediately on startup, without briefly showing the root page where the Identity Server redirects to?

Relevant extra information

Here's my custom _app.js component, which seems relevant because it is a component that triggers handleRedirectPromise which causes the redirect to intended page:

export default function MyApp({ Component, pageProps }) {
  return (
    <MsalProvider instance={msalInstance}>
      <PageHeader></PageHeader>
      <Component {...pageProps} />
    </MsalProvider>
  );
}

PS. To help folks searching online find this question: the behavior is triggered by navigateToLoginRequestUrl: true (is the default) in the configuration. Setting it to false plainly disables sending the user to the intended page at all.

Attempted solutions with middleware

I figured based on how APP_INITIALIZERs work in Angular, to use middleware like this at some point:

// From another file:
// export const msalInstance = new PublicClientApplication(msalConfig);

export async function middleware(_request) {
    const targetUrlAfterLoginRedirect = await msalInstance.handleRedirectPromise()
        .then((result) => {
            if (!!result && !!result.state) {
                return result.state;
            }
            return null;
        });

    console.log('Found intended target before login flow: ', targetUrlAfterLoginRedirect);

    // TODO: Send user to the intended page with router.
}

However, this logs on the server's console:

Found intended target before login flow: null

So it seems middleware is too early for msal-react to cope with? Shame, because middleware would've been perfect, to allow as much SSR for target pages as possible.

It's not an option to change the redirect URL on B2C's side, because I'll be constantly adding new routes to my app that need this behavior.

Note that I also tried to use middleware to just sniff out the state myself, but since the middleware runs on Node it won't have access to the hash fragment.

Animated GIF showing the flashing home page

Here's an animated gif that shows the /home page is briefly (200ms or so) shown before /me is properly opened. Warning, gif is a wee bit flashy so in a spoiler tag:

screen capture from a browser, of the steps in the question

Attempted solution with custom NavigationClient

I've tried adding a custom NavigationClient to more closely mimic the nextjs sample from Microsoft's repository, like this:

import { NavigationClient } from "@azure/msal-browser";

// See: https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-react/docs/performance.md#how-to-configure-azuremsal-react-to-use-your-routers-navigate-function-for-client-side-navigation
export class CustomNavigationClient extends NavigationClient {
  constructor(router) {
    super();
    this.router = router;
  }

  async navigateInternal(url, options) {
    console.log('???? Navigating Internal to', url);
    const relativePath = url.replace(window.location.origin, "");
    if (options.noHistory) {
      this.router.replace(relativePath);
    } else {
      this.router.push(relativePath);
    }

    return false;
  }
}

This did not solve the issue. The console.log is there allowing me to confirm this code is not run on the server, as the Node logs don't show it.

Attempted solution: go through MSAL's SSR docs

Another thing I've tried is going through the documentation claiming @azure/msal-react supports Server Side Rendering (SSR) but those docs nor the linked samples demonstrate how to solve my issue.

Attempted solution in _app.tsx

Another workaround I considered was to sniff out the hash fragment client side when the user returns to my app (and make sure the intended page is also in that state). I can successfully send the OpenID state to B2C like this...

const extendedAuthenticationRequest = {
  ...authenticationRequest,
  state: `~path~${asPath}~path~`,
};

...and see it returned in the Network tab of the dev tools.

However, when I try to extract it in my _app.tsx still doesn't work. I tried this code from another Stack Overflow answer to get the .hash:

const [isMounted, setMounted] = useState(false);

useEffect(() => {
  if (isMounted) {
    console.log('====> saw the following hash', window.location.hash);
    const matches = /~path~(.+)~path~/.exec(window.location.hash);
    if (matches && matches.length > 0 && matches[1]) {
      const targetUrlAfterOpenIdRedirect = decodeURIComponent(matches[1]);
      console.log("Routing to", targetUrlAfterOpenIdRedirect);
      router.replace(targetUrlAfterOpenIdRedirect);
    }
  } else {
    setMounted(true);
  }
}, [isMounted]);

if (!isMounted) return null;

// else: render <MsalProvider> and the intended page component

This does find the intended page from the state and executes routing, but still flashes the /home page before going to the intended page.

Footnote: related GitHub issue

Submitted an issue at MSAL's GitHub repository too.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。
列表为空,暂无数据
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文