配置第三方 OAuth IdP 身份验证

注意

若要使身份验证适用于移动客户端上的选项卡,请确保使用版本 1.4.1 或更高版本的 Microsoft Teams JavaScript 客户端库 (TeamsJS) 。

Microsoft Teams 应用可能需要与各种服务(如 Facebook、Twitter 和 Teams)交互。 其中大多数服务都需要身份验证和授权进行访问。 Teams 使用 Microsoft Graph 将用户配置文件信息存储在 Microsoft Entra ID 中。 本文主要介绍如何使用 Microsoft Entra ID 进行身份验证来访问此信息。

Microsoft Entra ID 和许多其他服务提供商使用 OAuth 2.0,这是身份验证的开放标准。 在 Teams 中处理身份验证和Microsoft Entra ID 时,必须了解 OAuth 2.0。 提供的示例采用 OAuth 2.0 隐式授权流,该流从 Microsoft Entra ID 和 Microsoft Graph 中检索用户的配置文件信息。

本文中的代码来自 Teams 示例应用 Microsoft Teams 身份验证示例 (Node) 。 它包含一个静态选项卡,该选项卡请求 Microsoft Graph 的访问令牌,并显示来自 entra ID Microsoft当前用户的基本配置文件信息。

有关选项卡身份验证流的概述,请参阅 选项卡中的身份验证流

选项卡中的身份验证流不同于机器人中的身份验证流。

注意

本主题反映 2.0.x 版的 Microsoft Teams JavaScript 客户端库 (TeamsJS) 。 如果使用的是早期版本,请参阅 TeamsJS 库概述 ,获取有关最新 TeamsJS 与早期版本之间的差异的指导。

将应用配置为将Microsoft Entra ID 用作标识提供者

支持标识提供者的 OAuth 2.0 不会对来自未注册应用程序的请求进行身份验证。 因此,必须提前注册应用程序。 若要使用 Microsoft Entra ID 注册应用程序,请执行以下步骤:

  1. 打开“应用程序注册门户”。

  2. 选择应用以查看其属性,或选择“新建注册”按钮。 查找应用的 “重定向 URI” 部分。

  3. 从下拉菜单中选择“ Web ”,并将 URL 更新到身份验证终结点。 在 GitHub 上提供的 TypeScript/Node.js 和 C# 示例应用中,重定向 URL 遵循类似的模式:

    重定向 URL:https://<hostname>/bot-auth/simple-start

将 替换为 <hostname> 实际主机。 此主机可以是专用托管站点,例如 Azure、故障或开发计算机上的 ngrok 隧道,例如 abcd1234.ngrok.io。 如果没有此信息,请确保完成或托管应用 (或示例应用) 。 拥有此信息后,请继续此过程。

注意

可以选择任何第三方 OAuth 提供程序,例如LinkedIn、Google 等。 为这些提供程序启用身份验证的过程类似于使用 Microsoft Entra ID 作为第三方 OAuth 提供程序。 有关使用任何第三方 OAuth 提供程序的详细信息,请访问特定提供程序的网站。

启动身份验证流

注意

如果启用了 实验性第三方存储分区 ,则第三方身份验证将失败。 应用会反复提示进行身份验证,因为值不会存储在本地。

通过用户操作触发身份验证流。 避免自动打开身份验证弹出窗口,因为这样做可能会触发浏览器的弹出窗口阻止程序并混淆用户。

将按钮添加到配置或内容页,使用户能够在需要时登录。 可在选项卡的配置页或任何内容页上完成此操作。

与大多数标识提供者一样,Microsoft Entra ID 不允许将其内容置于 中 iframe。 这意味着你需要添加一个页面来托管 Teams 客户端在弹出窗口中显示的标识提供者。 在以下示例中,页面为 /tab-auth/simple-startauthentication.authenticate()选择按钮时,使用 TeamsJS 库的 函数启动此页面。

import { authentication } from "@microsoft/teams-js";
authentication.authenticate({
    url: window.location.origin + "/tab-auth/simple-start-v2",
    width: 600,
    height: 535})
.then((result) => {
    console.log("Login succeeded: " + result);
    let data = localStorage.getItem(result);
    localStorage.removeItem(result);
    let tokenResult = JSON.parse(data);
    showIdTokenAndClaims(tokenResult.idToken);
    getUserProfile(tokenResult.accessToken);
})
.catch((reason) => {
    console.log("Login failed: " + reason);
    handleAuthError(reason);
});

注释

  • 传递给 authenticate() 的 URL 是身份验证流的起始页。 在此示例中,它是 /tab-auth/simple-start。 这应与 在 Microsoft Entra 应用程序注册门户中注册的内容匹配。

  • 身份验证流必须从域上的页面开始。 此域还应在清单的 validDomains 部分中列出。 失败会导致出现空弹出窗口。

  • 如果无法使用 authenticate(),则弹出窗口可能不会在登录过程结束时关闭,从而导致问题。

显示弹出页 (/tab-auth/simple-start) 时,将运行以下代码。 页面的主要目标是重定向到标识提供者,以便用户可以登录。 可以使用 HTTP 302 在服务器端完成此重定向,但在这种情况下,可以使用调用 window.location.assign()在客户端上完成。 这还允许 app.getContext() 用于检索提示信息,这些信息可以传递给Microsoft Entra ID。

app.getContext().then((context) => {
    // Generate random state string and store it, so we can verify it in the callback
    let state = _guid(); // _guid() is a helper function in the sample
    localStorage.setItem("simple.state", state);
    localStorage.removeItem("simple.error");

    // Go to the Azure AD authorization endpoint
    let queryParams = {
        client_id: "{{appId}}",
        response_type: "id_token token",
        response_mode: "fragment",
        scope: "https://graph.microsoft.com/User.Read openid",
        redirect_uri: window.location.origin + "/tab/simple-end",
        nonce: _guid(),
        state: state,
        // The context object is populated by Teams; the loginHint attribute
        // is used as hinting information
        login_hint: context.user.loginHint,
    };

    let authorizeEndpoint = `https://login.microsoftonline.com/${context.user.tenant.id}/oauth2/v2.0/authorize?${toQueryString(queryParams)}`;
    window.location.assign(authorizeEndpoint);
});

完成授权后,用户将重定向到在 /tab-auth/simple-end 上为应用指定的回调页。

注释

  • 有关生成身份验证请求和 URL 的帮助,请参阅获取用户上下文信息。 例如,可以使用用户的登录名作为 login_hint Microsoft Entra 登录的值,这意味着用户可能需要少键入。 请记住,不应直接使用此上下文作为身份证明,因为攻击者可以在恶意浏览器中加载页面,并为其提供所需的任何信息。
  • 尽管选项卡上下文提供有关用户的有用信息,但无论将其作为选项卡内容 URL 的 URL 参数获取,还是通过调用 app.getContext() Microsoft Teams JavaScript 客户端库中的 函数 (TeamsJS) ,都不要使用此信息对用户进行身份验证。 恶意行动者可以使用自己的参数来调用选项卡内容 URL,而模拟 Microsoft Teams 的网页可能会在 iframe 中加载选项卡内容 URL,并将其自己的数据返回给 getContext() 函数。 应将选项卡上下文中与身份相关的信息视为提示,并在使用前对其进行验证。
  • state 参数用于确认调用回调 URI 的服务就是你调用的服务。 state如果回调中的参数与调用期间发送的参数不匹配,则不会验证返回调用,应终止。
  • 无需在应用的 manifest.json 文件中的 validDomains 列表中包括标识提供者的域。

回调页

在上一部分中,你调用了 Microsoft Entra 授权服务,并传入了用户和应用信息,以便Microsoft Entra ID 可以向用户提供其自己的整体授权体验。 你的应用无法控制此体验中发生的情况。 它所知道的只是当Microsoft Entra ID 调用你提供的回调页时返回 (/tab-auth/simple-end) 。

在此页中,需要根据Microsoft Entra ID 返回的信息来确定成功或失败,并调用 authentication.notifySuccess()authentication.notifyFailure()。 如果登录成功,则有权访问服务资源。

// Split the key-value pairs passed from Azure AD
// getHashParameters is a helper function that parses the arguments sent
// to the callback URL by Azure AD after the authorization call
let hashParams = getHashParameters();
if (hashParams["error"]) {
    // Authentication/authorization failed
    localStorage.setItem("simple.error", JSON.stringify(hashParams));
} else if (hashParams["access_token"]) {
    // Get the stored state parameter and compare with incoming state
    let expectedState = localStorage.getItem("simple.state");
    if (expectedState !== hashParams["state"]) {
        // State does not match, report error
        localStorage.setItem("simple.error", JSON.stringify(hashParams));
        authentication.notifyFailure("StateDoesNotMatch");
    } else {
        // Success -- return token information to the parent page.
        // Use localStorage to avoid passing the token via notifySuccess; instead we send the item key.
        let key = "simple.result";
        localStorage.setItem(key, JSON.stringify({
            idToken: hashParams["id_token"],
            accessToken: hashParams["access_token"],
            tokenType: hashParams["token_type"],
            expiresIn: hashParams["expires_in"]
        }));
        authentication.notifySuccess(key);
    }
} else {
    // Unexpected condition: hash does not contain error or access_token parameter
    localStorage.setItem("simple.error", JSON.stringify(hashParams));
    authentication.notifyFailure("UnexpectedFailure");
}

此代码使用帮助程序函数分析从 Microsoft Entra ID 接收 window.location.hashgetHashParameters() 键值对。 如果它找到 access_token,并且 state 值与身份验证流开始时提供的值相同,则它会通过调用 notifySuccess() 将访问令牌返回给选项卡,否则会报告错误并显示 notifyFailure()

注释

NotifyFailure() 具有以下预定义的失败原因:

  • CancelledByUser:用户在完成身份验证流之前关闭了弹出窗口。

    注意

    建议不要对Cross-Origin-Opener-Policy登录页上的响应标头使用 same-originsame-origin-allow-popups 值,因为这会中断与父窗口的连接,并导致身份验证 API 调用过早返回并出现CancelledByUser错误。

  • FailedToOpenWindow 无法打开弹出窗口。 在浏览器中运行 Microsoft Teams 时,这通常意味着弹出窗口阻止程序已阻止窗口。

如果成功,则可以刷新或重新加载页面,并显示与现在经过身份验证的用户相关的内容。 如果身份验证失败,则会显示一条错误消息。

你的应用可以设置自己的会话 Cookie,以便用户在返回到当前设备上的选项卡时无需再次登录。

注意

  • 计划于 2020 年初发布的 Chrome 80 引入了新的 Cookie 值并默认实施 Cookie 策略。 建议为 Cookie 设置预期用途,而不是依赖默认浏览器行为。 请参阅SameSite cookie 属性 (2020 更新)
  • 若要为Microsoft Teams 免费版和来宾用户获取适当的令牌,请确保应用使用特定于租户的终结点 https://login.microsoftonline.com/**{tenantId}**。 可以从机器人消息或选项卡上下文中获取 tenantId。 如果应用使用 https://login.microsoftonline.com/common,用户可能会收到不正确的令牌,导致他们登录到“主”租户,而不是登录到的租户。

有关单一登录 (SSO) 的详细信息,请参阅 无提示身份验证一文。

代码示例

显示使用 entra ID Microsoft选项卡身份验证过程的示例代码:

示例名称 Description .NET Node.js 清单
选项卡 SSO 此示例应用显示 Teams 中选项卡Microsoft Entra SSO。 View 视图
Teams 工具包
不适用
选项卡、机器人和消息扩展 (ME) SSO 此示例演示 Tab、机器人和 ME 的 SSO- 搜索、操作和链接展开。 View View View

另请参阅