调用 Web API 的桌面应用:以交互方式获取令牌

下面的示例展示了以交互方式获取令牌,以便使用 Microsoft Graph 读取用户配置文件的最少代码。

MSAL.NET 中的代码

string[] scopes = new string[] { "user.read" };

var app = PublicClientApplicationBuilder.Create("YOUR_CLIENT_ID")
    .WithDefaultRedirectUri()
    .Build();

var accounts = await app.GetAccountsAsync();

AuthenticationResult result;
try
{
    result = await app.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
      .ExecuteAsync();
}
catch (MsalUiRequiredException)
{
    result = await app.AcquireTokenInteractive(scopes).ExecuteAsync();
}

必需参数

AcquireTokenInteractive 只有一个必需的参数:scopes。 它包含用于定义所需令牌范围的字符串枚举。 如果令牌用于 Microsoft Graph,则可以在“权限”一节中每个 Microsoft Graph API 的 API 参考中找到所需范围。例如,若要列出用户的联系人,必须使用 User.ReadContacts.Read 作为范围。 有关详细信息,请参阅 Microsoft Graph 权限参考

在桌面和移动应用程序上,请务必使用 .WithParentActivityOrWindow 指定父级。 在许多情况下,这是一项要求,并且 MSAL 将引发异常。

对于桌面应用程序,请参阅父窗口句柄

对于移动应用程序,请提供 Activity (Android) 或 UIViewController (iOS)。

MSAL.NET 中的可选参数

WithParentActivityOrWindow

UI 非常重要,因为它是交互式的。 AcquireTokenInteractive 具有一个特定的可选参数,该参数可(为支持它的平台)指定父 UI。 在桌面应用程序中使用 .WithParentActivityOrWindow 时,它具有不同的类型,具体取决于平台。

或者,如果你不想控制登录对话框在屏幕上的显示位置,则可以省略可选的父窗口参数来创建窗口。 此选项适用于基于命令行,用于将调用传递给任何其他后端服务,并且不需要任何窗口进行用户交互的应用程序。

// net45
WithParentActivityOrWindow(IntPtr windowPtr)
WithParentActivityOrWindow(IWin32Window window)

// Mac
WithParentActivityOrWindow(NSWindow window)

// .NET Standard (this will be on all platforms at runtime, but only on .NET Standard platforms at build time)
WithParentActivityOrWindow(object parent).

备注:

  • 在 .NET Standard 中,预期的 object 值在 Android 上为 Activity,在 iOS 上为 UIViewController,在 Mac 上为 NSWindow,在 Windows 上为 IWin32WindowIntPr

  • 在 Windows 上,必须从 UI 线程调用 AcquireTokenInteractive,以便嵌入性浏览器获取正确的 UI 同步上下文。 不从 UI 线程调用可能会导致消息无法正确抽取并导致 UI 出现死锁情况。 如果你不在 UI 线程上,从 UI 线程调用 Microsoft 身份验证库 (MSAL) 的一种方法是在 Windows Presentation Foundation (WPF) 上使用 Dispatcher

  • 使用 WPF 时,若要从 WPF 控件获取一个窗口,可以使用 WindowInteropHelper.Handle 类。 然后从 WPF 控件 (this) 发出调用:

    result = await app.AcquireTokenInteractive(scopes)
                      .WithParentActivityOrWindow(new WindowInteropHelper(this).Handle)
                      .ExecuteAsync();
    

WithPrompt

使用 WithPrompt() 通过指定提示来控制与用户的交互。 可以使用 Microsoft.Identity.Client.Prompt 结构控制确切的行为。

结构定义以下常量:

  • SelectAccount 强制安全令牌服务 (STS) 显示帐户选择对话,该对话包含用户具有会话的帐户。 此选项为默认值。 当你想要让用户在不同标识中进行选择时,这很有用。

    此选项会驱动 MSAL 向标识提供者发送 prompt=select_account。 它可以根据可用的信息(例如用户的帐户以及用户会话是否存在)提供最佳体验。 不要对其进行更改,除非你有充分的理由。

  • 利用 Consent 可以强制提示用户许可,即使应用程序之前已许可。 在这种情况下,MSAL 会将 prompt=consent 发送到标识提供者。 可以在某些注重安全的应用程序中使用此选项,这些应用程序中的组织监管机制要求每次在用户打开应用程序时,都要显示许可对话框。

  • 利用 ForceLogin 可以让应用程序提示用户提供凭据,即使此用户提示可能并无必要。 如果令牌获取失败,此选项对让用户再次登录很有帮助。 在这种情况下,MSAL 会将 prompt=login 发送到标识提供者。 组织有时会在某些注重安全的应用程序中使用此选项,该应用程序中的组织监管机制要求用户每次在访问应用程序的特定部分时都要进行登录。

  • Create 通过向标识提供程序发送 prompt=create 触发用于外部标识的注册体验。 Azure Active Directory B2C (Azure AD B2C) 应用不应发送此提示。 有关详细信息,请参阅向应用添加自助注册用户流

  • Never(仅适用于 .NET 4.5 和 Windows 运行时)不提示用户。 而是尝试使用存储在隐藏嵌入式 Web 视图中的 Cookie。

    使用此选项可能会失败。 在这种情况下,AcquireTokenInteractive 将引发异常,通知需要 UI 交互。 然后,请使用另一个 Prompt 参数。

  • NoPrompt 不会向标识提供者发送任何提示。 标识提供者决定哪种登录体验最适合用户(单一登录或选择帐户)。

    在 Azure AD B2C 中编辑配置文件策略需要此选项。 有关详细信息,请参阅 Azure AD B2C 细节

WithUseEmbeddedWebView

通过此方法,可以指定是否要强制使用嵌入式 Web 视图或系统 Web 视图(可用时)。 有关详细信息,请参阅 Web 浏览器的用法

var result = await app.AcquireTokenInteractive(scopes)
                    .WithUseEmbeddedWebView(true)
                    .ExecuteAsync();

WithExtraScopeToConsent

此修饰符适用于你希望用户事先同意多个资源,而你不想使用逐步同意的高级方案。 开发人员通常对 MSAL.NET 和 Microsoft 标识平台使用逐步同意。 有关详细信息,请参阅让用户提前同意多个资源

var result = await app.AcquireTokenInteractive(scopesForCustomerApi)
                     .WithExtraScopeToConsent(scopesForVendorApi)
                     .ExecuteAsync();

WithCustomWebUi

Web UI 是一种调用浏览器的机制。 此机制可以是专用的 UI WebBrowser 控件,也可以是委托打开浏览器的方法。 MSAL 为大多数平台提供 Web UI 实现,但在以下情况下,你可能想要自行托管浏览器:

  • 你有 MSAL 未显式涵盖的平台,例如桌面上的 Blazor、Unity 和 Mono。
  • 你希望对应用程序进行 UI 测试,并使用可以与 Selenium 配合使用的自动化浏览器。
  • 浏览器和运行 MSAL 的应用位于不同的进程中。

若要实现此目的,需要向 MSAL 提供 start Url,这需要在浏览器中显示,以便用户可以输入用户名等项目。 身份验证完成后,应用需要向 MSAL 回传 end Url,其中包含 Microsoft Entra ID 提供的代码。 end Url 的宿主始终为 redirectUri。 若要截获 end Url,请执行以下操作之一:

  • 监视浏览器重定向,直至进入 redirect Url
  • 让浏览器重定向到你所监视的 URL。

WithCustomWebUi 是一个扩展点,可用于在公共客户端应用程序中提供你自己的 UI。 你还可以让用户通过标识提供者的 /Authorize 终结点,并让他们登录并同意。 然后,MSAL.NET 可以兑换身份验证代码并获取令牌。

例如,可以在 Visual Studio 中使用 WithCustomWebUi 让 Electron 应用程序(例如 Visual Studio 反馈)提供 Web 交互,但会将大部分工作留给 MSAL.NET 来完成。 如果要提供 UI 自动化,也可以使用 WithCustomWebUi

在公共客户端应用程序中,MSAL.NET 使用代码交换的证明密钥 (PKCE) 标准来确保遵守规则。 只有 MSAL.NET 才能兑换代码。 有关详细信息,请参阅 RFC 7636 - OAuth 公共客户端的代码交换证明密钥

using Microsoft.Identity.Client.Extensions;
使用 WithCustomWebUI

若要使用 WithCustomWebUI,请执行以下步骤:

  1. 实现 ICustomWebUi 接口。 有关详细信息,请参阅此 GitHub 页面

  2. 实现一个 AcquireAuthorizationCodeAsync 方法,并接受 MSAL.NET 计算的授权代码 URL。

  3. 让用户与身份提供者进行交互,并返回身份提供者用于回调实现的 URL 以及授权代码。 如果遇到问题,实现应引发 MsalExtensionException 异常,以便与 MSAL 进行配合。

  4. AcquireTokenInteractive 调用中,通过传递自定义 Web UI 的实例使用 .WithCustomUI() 修饰符:

    result = await app.AcquireTokenInteractive(scopes)
                      .WithCustomWebUi(yourCustomWebUI)
                      .ExecuteAsync();
    

MSAL.NET 团队已重新编写 UI 测试,以使用此扩展性机制。 如果你感兴趣,请查看 MSAL.NET 源代码中的 SeleniumWebUI 类。

通过 SystemWebViewOptions 提供极佳体验

在 MSAL.NET 4.1 SystemWebViewOptions 中,可以指定:

  • 系统 Web 浏览器中出现登录或同意错误时要转到的 URI (BrowserRedirectError) 或要显示的 HTML 片段 (HtmlMessageError)。
  • 登录或同意成功时要转到的 URI (BrowserRedirectSuccess) 或要显示的 HTML 片段 (HtmlMessageSuccess)。
  • 启动系统浏览器所需运行的操作。 可以通过设置 OpenBrowserAsync 委托来提供自己的实现。 该类还为两种浏览器提供了默认实现:为 Microsoft Edge 提供了 OpenWithEdgeBrowserAsync,为基于 Chromium 的 Microsoft Edge 提供了 OpenWithChromeEdgeBrowserAsync

若要使用此结构,请编写如下示例所示的内容:

IPublicClientApplication app;
...

options = new SystemWebViewOptions
{
 HtmlMessageError = "<b>Sign-in failed. You can close this tab ...</b>",
 BrowserRedirectSuccess = "https://contoso.com/help-for-my-awesome-commandline-tool.html"
};

var result = app.AcquireTokenInteractive(scopes)
                .WithEmbeddedWebView(false)       // The default in .NET
                .WithSystemWebViewOptions(options)
                .Build();

其他可选参数

若要了解用于 AcquireTokenInteractive 的其他可选参数,请参阅 AcquireTokenInteractiveParameterBuilder

后续步骤

转到此方案中的下一篇文章:从桌面应用调用 Web API