在 iframe 内嵌的应用中使用 MSAL

默认情况下,当应用在 iframe 内呈现时,MSAL 会阻止全帧重定向到Microsoft Entra ID身份验证终结点,这意味着不能使用重定向 API 与 IdP 进行用户交互:

  • 实施此限制,因为Microsoft Entra ID将拒绝在 iframe 中呈现任何要求用户交互(例如凭据输入同意注销等)的提示,方法是引发X-FRAME OPTIONS SET TO DENY错误,这是防止点击劫持攻击所采取的措施。
  • 而是,如果需要用户交互,则必须依赖 MSAL 的弹出式 API;如果可以避免用户交互,则必须依赖无提示 API(ssoSilent()acquireTokenSilent())。
  • 同样,必须使用 logoutPopup() API 进行注销(:警告:如果你的应用使用的是低于 v2.13 的版本msal-browser,请确保升级并替换 logout() API,因为它将尝试完整帧重定向到 Microsoft Entra ID)。
  • 使用 弹出窗口 API 时,需要考虑到父应用施加的任何 沙盒 限制。 具体而言,当 iframe 处于沙盒模式时,父应用需要设置 allow-popups 标志。

Azure AD B2C 提供嵌入式登录体验,允许在 iframe 中呈现自定义登录 UI。 由于 MSAL 默认阻止 iframe 中的重定向,因此需要将 allowRedirectInIframe 配置选项设置为 true 才能使用此功能。 请注意,不建议在Microsoft Entra ID上为应用启用此选项,因为存在上述限制。

浏览器限制

由于 iframe 中的Microsoft Entra会话 Cookie 被视为第三方 Cookie,因此某些浏览器(例如处于隐身模式的 SafariChrome)默认会阻止或清除这些 Cookie。 这会影响 iframed 应用的 单一登录 体验,因为它们无法访问 IdP 的会话 Cookie(请参阅: 单一登录)。

此外,当 Chrome 中禁用第三方 Cookie 时,iframed MSAL 应用将无法访问本地或会话存储。 在这种情况下,MSAL 将回退到内存存储。

单一登录

可以在 iframe 内嵌应用与父应用之间实现单点登录,无论是同源还是跨源前提是您需将账户提示从父应用传递给 iframe 内嵌应用。

具有相同源的应用

如果两个应用都配置 MSAL 以使用 本地存储 进行缓存,则具有相同源的 Iframed 和父应用可能有权访问同一 MSAL.js 缓存实例,并且能够登录而不出现提示。 有关详细信息,请参阅: 使用 MSAL.js进行单一登录

具有跨源的应用

跨源的 iframe 应用和父应用可以使用 ssoSilent() API 来实现单点登录。 为此,父应用应将 帐户loginHint (用户名)或 会话 ID (sid)传递给 iframed 应用。

应用可以尝试在没有上述任何参数的情况下使用 ssoSilent 。 但是请注意,在使用时还有其他ssoSilent,而不提供有关用户会话的任何信息。

对于 iframed 和父应用之间的跨域通信,可以考虑以下几种替代方法:

  • 可以在父应用中将查询字符串添加到 iframe 的源,并在子应用中稍后检索它们:
// Create the main myMSALObj instance
// configuration parameters are located at authConfig.js
const myMSALObj = new msal.PublicClientApplication({
    auth: {
        clientId: "ENTER_CLIENT_ID",
        authority: "https://login.microsoftonline.com/ENTER_TENANT_ID",
        redirectUri: "/redirect", // set to a blank page for handling auth code response via popups
    },
    cache: {
        cacheLocation: "localStorage", // set your cache location to local storage
    },
});

window.onload = () => {
    
    const urlParams = new URLSearchParams(window.location.search);
    const sid = urlParams.get("sid");

    // attempt SSO
    myMSALObj.ssoSilent({
        sid: sid
    }).then((response) => {
        // do something with response
    }).catch(error => {
        // handle errors
    });
}
  • 可以在父应用中使用 postMessage() API,并在子应用中侦听消息事件:
// Create the main myMSALObj instance
// configuration parameters are located at authConfig.js
const myMSALObj = new msal.PublicClientApplication({
    auth: {
        clientId: "ENTER_CLIENT_ID",
        authority: "https://login.microsoftonline.com/ENTER_TENANT_ID",
        redirectUri: "/redirect", // set to a blank page for handling auth code response via popups
    },
    cache: {
        cacheLocation: "localStorage", // set your cache location to local storage
    },
});

const parentDomain = "http://localhost:3001";

window.addEventListener("message", (event) => {
    // check the origin of the data
    if (event.origin === parentDomain) {
        const sid = event.data;

        // attempt SSO
        myMSALObj.ssoSilent({
            sid: sid
        }).then((response) => {
            // do something with response
        }).catch(error => {
            // handle errors
        });
    }
});

错误处理

如果 ssoSilent() 失败,应捕获并处理任何错误。 具体而言:

  • InteractionRequiredError:如果需要同意,用户需要执行 MFA 等,将引发。通常,只需启动交互式 API 即可处理此错误。
  • BrowserAuthError:如果未提供 账户提示、提供了无效的 账户提示、弹出窗口被阻止等,则会引发此错误。你需要检查 errorCode 并进行相应处理。
    myMSALObj.ssoSilent({
        sid: sid
    }).then((response) => {
            // do something with response
        }).catch(error => {
            if (error instanceof msal.InteractionRequiredAuthError) {
                myMSALObj.loginPopup()
                    .then((response) => {
                        // do something with response
                    });
            } else if (error instanceof msal.BrowserAuthError) {
                if (error.errorCode === "silent_sso_error") {
                    // e.g. username is null
                }
                if (error.errorCode === "popup_window_error") {
                    // e.g. popups are blocked
                }
            } else {
                console.log(error);
            }
        });

用户交互

如果要最大程度地减少与需要用户交互的 IdP 的通信,或者出于任何原因遇到弹出窗口问题,则可以考虑以下一些选项:

  • 授予管理员同意。 这可确保用户首次登录时,不会出现针对你的应用所需权限的同意提示。

  • 预授权客户端应用。 这可确保在客户端应用调用 Web API 时,Web API 所需的权限没有许可提示。

单一退出登录

可以将 MSAL.js 与 前端通道注销 URI 配合使用,以实现 iframed 和父应用之间的 单一注销 效果。 例如,如果希望用户在从父应用退出登录时,也自动从嵌入在 iframe 中的应用退出登录,则应为这些应用启用前端通道注销。 为此,请参阅: 如何配置前端通道注销 URI