配置第三方 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 注册应用程序,请执行以下步骤:
打开“应用程序注册门户”。
选择应用以查看其属性,或选择“新建注册”按钮。 查找应用的 “重定向 URI” 部分。
从下拉菜单中选择“ 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-start
。
authentication.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.hash
的 getHashParameters()
键值对。 如果它找到 access_token
,并且 state
值与身份验证流开始时提供的值相同,则它会通过调用 notifySuccess()
将访问令牌返回给选项卡,否则会报告错误并显示 notifyFailure()
。
注释
NotifyFailure()
具有以下预定义的失败原因:
CancelledByUser
:用户在完成身份验证流之前关闭了弹出窗口。注意
建议不要对
Cross-Origin-Opener-Policy
登录页上的响应标头使用same-origin
或same-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 |