Настроить страницу-мост для перенаправления в MSAL Browser

В этом руководстве приведены инструкции для конкретных фреймворков по настройке страницы-посредника для перенаправления, введённой в MSAL Browser v5. Сведения о том, почему требуется мост перенаправления, см. в руководстве по миграции версии 4 до версии 5.

Предупреждение

Страница моста перенаправления не должна обслуживаться заголовками Cross-Origin-Opener-Policy . Страница-посредник — это промежуточная страница, которая получает ответ аутентификации после того, как IdP завершает поток OAuth. Если на странице моста заданы заголовки COOP, браузер выполняет смену группы контекстов просмотра, что разрывает канал связи с основным приложением, — тем самым вновь создавая ту самую проблему, которую мост и предназначен решать.

Important

После обновления redirectUri так, чтобы он указывал на новую страницу моста перенаправления, обязательно также обновите URI перенаправления в регистрации вашего приложения Entra ID. Универсальный код ресурса (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, а страница перенаправления не должна выполнять вызовы API, которые могли бы вызвать MsalInterceptor (или иным образом вызывать API MSAL):
import { RedirectComponent } from "./redirect/redirect.component";

const routes: Routes = [
    { path: "redirect", component: RedirectComponent },
    // ... your other routes
];
  1. Убедитесь, что сборка включает компонент. При angular.json использовании компонента маршрута Angular не требуется никаких изменений. Интерфейс командной строки Angular автоматически объединяет компонент. Если вы предпочитаете статический redirect.html вместо компонента, связанного с маршрутом, добавьте его в массив assets:
// angular.json
{
    "projects": {
        "your-app": {
            "architect": {
                "build": {
                    "options": {
                        "assets": [
                            { "glob": "**/*", "input": "public" },
                            "src/redirect.html" // ← Add redirect bridge page
                        ]
                    }
                }
            }
        }
    }
}

Образец: См. пример angular-standalone-sample и angular-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. В production-сборках Rollup создает в выходном каталоге и index.html, и redirect.html.

Образец: См. пример react-router-sample, typescript-sample и b2c-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 Router и App Router.

Маршрутизатор страниц (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. Исключите страницу перенаправления из MsalProvider в _app.js:
// 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 автоматически обслуживает страницы.

Пример: См. nextjs-sample для примера с Pages Router.

Серверная часть на 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.

Дополнительные ресурсы