MSAL - PublicClientApplication - GetAccountsAsync() return 没有任何帐户

MSAL - PublicClientApplication - GetAccountsAsync() doesn't return any Accounts

我正在开发一个小的 WPF 应用程序,它应该从 MS Graph API 查询一些数据。我想使用 SSO,因此用户不必单独登录应用程序。

该应用程序 运行 在已加入 Azure AD 的设备上。该用户是 AADC 同步的 AD 用户。 AAD 租户与 ADFS 联合。用户使用 Hello for Business (PIN) 或密码进行身份验证。结果问题还是一样。

我可以确认用户通过以下方式获得了 PRT:

dsregcmd /status

AzureAdPrt: YES

以防万一:Azure AD 中的应用程序注册设置为 "Treat application as public client"。并配置了以下重定向 URI:

根据我找到的示例,我正在使用以下代码尝试获取访问令牌。但是 GetAccountsAsync() 方法不会 return 任何用户,也不会抛出任何错误或异常。

谁能告诉我,我在这里缺少什么?

如有任何帮助,我们将不胜感激!

PS:当我使用 "Interactive Authentication" 尝试时它工作正常。

public GraphAuthProvider(string appId, string tenantId, string[] scopes)
{
    _scopes = scopes;

    try
    {
        _msalClient = PublicClientApplicationBuilder
                .Create(appId)
                .WithAuthority(AadAuthorityAudience.AzureAdMyOrg, true)
                .WithTenantId(tenantId)
                .Build();
    }
    catch (Exception exception)
    {
        _log.Error(exception.Message);
        _log.Error(exception.StackTrace);
        throw;
    }
}

public async Task<string> GetAccessToken()
{
    _log.Info("Starting 'GetAccessToken'...");
    var accounts = await _msalClient.GetAccountsAsync();
    _userAccount = accounts.FirstOrDefault();


    // If there is no saved user account, the user must sign-in
    if (_userAccount == null)
    {
        _log.Info("No cached accounts found. Trying integrated authentication...");
        [...]
    }
    else
    {
        // If there is an account, call AcquireTokenSilent
        // By doing this, MSAL will refresh the token automatically if
        // it is expired. Otherwise it returns the cached token.

        var userAccountJson = await Task.Factory.StartNew(() => JsonConvert.SerializeObject(_userAccount));
        _log.Info($"Found cached accounts. _userAccount is: {userAccountJson}");

        var result = await _msalClient
            .AcquireTokenSilent(_scopes, _userAccount)
            .ExecuteAsync();

        return result.AccessToken;
    }
}

为了能够从 MSAL(访问缓存)返回 IAccounts,它必须在某个时刻引导缓存。您错过了起点,在您的情况下是 AcquireTokenInteractive

建议在 MSAL 上使用以下 try/catch 模式:

try
{
    var accounts = await _msalClient.GetAccountsAsync();

    // Try to acquire an access token from the cache. If an interaction is required, MsalUiRequiredException will be thrown.
    result = await _msalClient.AcquireTokenSilent(scopes, accounts.FirstOrDefault())
                .ExecuteAsync();
}
catch (MsalUiRequiredException)
{
    // Acquiring an access token interactively. MSAL will cache it so you can use AcquireTokenSilent on future calls.
    result = await _msalClient.AcquireTokenInteractive(scopes)
                .ExecuteAsync();
}

使用此 try/catch 模式代替您的 if/else 逻辑,您会很高兴。

为了进一步参考,msal desktop samples 涵盖了很多常见的场景。

更新

如果您在每个操作上都实例化一个新的 _msalClient,那么这就解释了为什么其他调用不起作用。您可以将 _msalClient 作为 static/singleton 实例或实现序列化令牌缓存。 Here is a cache example

由于评论中有一些关于非交互式身份验证的问题,所以我最终是这样工作的:

使用 WAM 并像这样配置生成器:

var builder = PublicClientApplicationBuilder.Create(ClientId)
            .WithAuthority($"{Instance}{TenantId}")
            .WithDefaultRedirectUri()
            .WithWindowsBroker(true);

在 Azure 应用程序注册中配置此重定向 URI:

ms-appx-web://microsoft.aad.brokerplugin/{client_id}

代码示例可用here

In-case 任何人都有类似的问题,我遇到了 GetAccountAsync 和 GetAccountsAsync(后者现在已弃用)有时都返回 null 的问题。我需要做的就是确保我所有的身份验证相邻库都是最新的。

我认为存在 in-memory 令牌缓存并不总是按预期工作的问题,似乎可以通过简单的更新解决。