在 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,因此某些浏览器(例如处于隐身模式的 Safari 或 Chrome)默认会阻止或清除这些 Cookie。 这会影响 iframed 应用的 单一登录 体验,因为它们无法访问 IdP 的会话 Cookie(请参阅: 单一登录)。

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

单一登录

你可以在同源的 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 应用注销,你应为 iframe 应用启用前端通道注销。 为此,请参阅: 如何配置前端通道注销 URI。