在 MSAL 浏览器中设置重定向桥页

本指南提供针对特定框架的说明,介绍如何设置 MSAL Browser v5 中引入的重定向桥页。 有关为何需要重定向桥的背景信息,请参阅 v4 到 v5 迁移指南

Warning

重定向桥接页面不得附带 Cross-Origin-Opener-Policy 标头提供。 桥页是 IdP 完成 OAuth 流后接收身份验证响应的中介。 如果在桥接页面上设置了 COOP 标头,浏览器会执行一次浏览上下文组切换,从而切断与主应用程序之间的通信通道,再次引入这个桥接页面原本就是为了解决的那个问题。

Important

redirectUri更新为指向新的重定向桥接页后,您还必须同时更新Entra ID 应用注册中的重定向 URI。 URI 必须 完全 匹配 ,包括路径、协议和端口。 无法更新应用注册将导致 redirect_uri_mismatch 错误。

Angular

  1. 创建重定向桥组件src/app/redirect/redirect.component.ts):
import { Component, OnInit } from "@angular/core";
import { broadcastResponseToMainFrame } from "@azure/msal-browser/redirect-bridge";

@Component({
    selector: "app-redirect",
    standalone: true,
    template: "<p>Processing authentication...</p>",
})
export class RedirectComponent implements OnInit {
    ngOnInit(): void {
        broadcastResponseToMainFrame().catch((error: Error) => {
            console.error("Error broadcasting response to main frame:", error);
        });
    }
}
  1. /redirect 路由配置中添加路由 。 重定向路由必须位于 MsalGuard外部,并且重定向页面不应发起会触发 MsalInterceptor 的 API 调用(或以其他方式调用 MSAL API):
import { RedirectComponent } from "./redirect/redirect.component";

const routes: Routes = [
    { path: "redirect", component: RedirectComponent },
    // ... your other routes
];
  1. 确保构建中包含该组件。 使用 Angular 路由组件时无需 angular.json 更改资产 - Angular CLI 会自动捆绑组件。 如果更喜欢静态 redirect.html 组件而不是路由组件,请将其添加到资产数组:
// angular.json
{
    "projects": {
        "your-app": {
            "architect": {
                "build": {
                    "options": {
                        "assets": [
                            { "glob": "**/*", "input": "public" },
                            "src/redirect.html" // ← Add redirect bridge page
                        ]
                    }
                }
            }
        }
    }
}

示例: 参见 angular-standalone-sampleangular-modules-sample

Vite

Vite 需要多页面配置,以便将 redirect.html 作为单独的入口点包含在构建输出中。

  1. 创建 redirect.html 在项目根目录中(旁边 index.html):
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Redirect</title>
</head>
<body>
    <p>Processing authentication...</p>
    <script type="module">
        import { broadcastResponseToMainFrame } from "@azure/msal-browser/redirect-bridge";

        broadcastResponseToMainFrame().catch((error) => {
            console.error("Error broadcasting response:", error);
        });
    </script>
</body>
</html>
  1. 更新 vite.config.ts 将重定向页添加为第二个条目:
import { defineConfig } from "vite";
import { resolve } from "path";

export default defineConfig({
    build: {
        rollupOptions: {
            input: {
                main: resolve(__dirname, "index.html"),
                redirect: resolve(__dirname, "redirect.html"), // ← Redirect bridge entry
            },
        },
    },
});

在开发期间(vite dev),会在 /redirect.html 自动提供该重定向页面。 在生产构建中,Rollup 会在输出目录中生成 index.htmlredirect.html

样品: 请参阅 react-router-sampletypescript-sampleb2c-sample

Webpack

Webpack 需要重定向页面的专用入口点和 HtmlWebpackPlugin 实例。

  1. 创建 src/redirect.html
<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Redirect</title>
</head>
<body>
    <p>Processing authentication...</p>
    <!-- The redirect script bundle will be injected by HtmlWebpackPlugin (see redirect.js entry). -->
</body>
</html>
  1. 创建 src/redirect.js (Webpack 入口点):
import { broadcastResponseToMainFrame } from "@azure/msal-browser/redirect-bridge";

broadcastResponseToMainFrame().catch((error) => {
    console.error("Error broadcasting response:", error);
});
  1. 更新 webpack.config.js
const HtmlWebpackPlugin = require("html-webpack-plugin");

module.exports = {
    entry: {
        main: "./src/index.js",
        redirect: "./src/redirect.js", // ← Redirect bridge entry
    },
    plugins: [
        new HtmlWebpackPlugin({
            filename: "index.html",
            template: "./src/index.html",
            chunks: ["main"],
        }),
        new HtmlWebpackPlugin({
            filename: "redirect.html",
            template: "./src/redirect.html",
            chunks: ["redirect"], // ← Only include the redirect chunk
        }),
    ],
};

Next.js

Next.js 页面自动成为路由,因此重定向桥是页面组件。 页面 路由器应用路由器的设置有所不同。

页面路由器 (pages/

  1. 创建 pages/redirect.js
import { useEffect } from "react";
import { broadcastResponseToMainFrame } from "@azure/msal-browser/redirect-bridge";

export default function Redirect() {
    useEffect(() => {
        broadcastResponseToMainFrame().catch((error) => {
            console.error("Error broadcasting response to main frame:", error);
        });
    }, []);

    return <p>Processing authentication...</p>;
}
  1. _app.js中,将重定向页面从MsalProvider中排除:
// pages/_app.js
import { useRouter } from "next/router";
import { MsalProvider } from "@azure/msal-react";

function MyApp({ Component, pageProps }) {
    const router = useRouter();

    // The redirect page must NOT be wrapped in MsalProvider
    if (router.pathname === "/redirect") {
        return <Component {...pageProps} />;
    }

    return (
        <MsalProvider instance={msalInstance}>
            <Component {...pageProps} />
        </MsalProvider>
    );
}

应用路由器 (app/

  1. 创建 app/redirect/page.js — 这必须是客户端组件("use client"):
"use client";

import { useEffect } from "react";
import { broadcastResponseToMainFrame } from "@azure/msal-browser/redirect-bridge";

export default function Redirect() {
    useEffect(() => {
        broadcastResponseToMainFrame().catch((error) => {
            console.error("Error broadcasting response to main frame:", error);
        });
    }, []);

    return <p>Processing authentication...</p>;
}
  1. 在根布局中,从 MsalProvider 排除重定向路由。 如果您的 app/layout.js 会将子元素包裹在 MsalProvider 中,请为该重定向路由单独创建一个布局以跳过它:
// app/redirect/layout.js — no MsalProvider wrapper
export default function RedirectLayout({ children }) {
    return <>{children}</>;
}

这样可以防止 MSAL 在 broadcastResponseToMainFrame() 运行之前处理身份验证响应哈希。


任何一个路由器都不需要 next.config.js 更改 - Next.js 自动提供页面。

示例: Pages 路由器示例请参见 nextjs-sample

Express.js / Node.js 后端

使用 Express.js(或任何提供静态文件的 Node.js 后端)时,请将服务器配置为在不使用 COOP 标头 的情况下 为重定向页提供服务:

const express = require("express");
const path = require("path");
const app = express();

// Serve the redirect bridge page WITHOUT COOP headers
app.get("/redirect", (req, res) => {
    res.sendFile(path.join(__dirname, "public", "redirect.html"));
});

// Set COOP headers for all other routes
app.use((req, res, next) => {
    res.setHeader("Cross-Origin-Opener-Policy", "same-origin");
    next();
});

app.use(express.static(path.join(__dirname, "public")));

样品: 请参阅 HybridSample

其他资源