配置第三方 OAuth IdP 身份验证

注意

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

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

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

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

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

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

注意

本主题反映 Microsoft Teams JavaScript 客户端库版本 2.0.x (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。 这意味着需要添加一个弹出页来托管标识提供者。 在以下示例中,此页为 /tab-auth/simple-startauthentication.authenticate()选择按钮时,使用 TeamsJS 库的 函数启动此页面。

import { authentication } from "@microsoft/teams-js";
authentication.authenticate({
    url: window.location.origin + "/tab/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) 显示时,将运行以下代码。 页面main目标是重定向到标识提供者,以便用户可以登录。 可以使用 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_hintMicrosoft 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.hash 接收的getHashParameters()键值对。 如果它找到 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) 的详细信息,请参阅 无提示身份验证一文。

代码示例

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

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

另请参阅