This commit is contained in:
Dev
2026-04-24 10:54:47 +02:00
19 changed files with 1113 additions and 51 deletions
@@ -45,14 +45,13 @@ public class GraphClientFactory
{
var pca = await _msalFactory.GetOrCreateAsync(clientId);
// When a tenant is specified we must NOT reuse cached accounts from /common
// (or a different tenant) — they route tokens to the wrong authority.
IAccount? account = null;
if (tenantId is null)
{
var accounts = await pca.GetAccountsAsync();
account = accounts.FirstOrDefault();
}
// Always reuse a cached account when one exists — `WithTenantId` on the
// silent/interactive call redirects the authority, and MSAL stores
// refresh tokens per tenant. Skipping the cached account forces an
// interactive prompt on every Graph call (the bug that produced 45
// sign-in windows during app registration).
var accounts = await pca.GetAccountsAsync();
var account = accounts.FirstOrDefault();
var graphScopes = scopes ?? new[] { "https://graph.microsoft.com/.default" };
@@ -68,7 +67,7 @@ public class GraphClientFactory
internal class MsalTokenProvider : IAccessTokenProvider
{
private readonly IPublicClientApplication _pca;
private readonly IAccount? _account;
private IAccount? _account;
private readonly string[] _scopes;
private readonly string? _tenantId;
@@ -87,19 +86,35 @@ internal class MsalTokenProvider : IAccessTokenProvider
Dictionary<string, object>? additionalAuthenticationContext = null,
CancellationToken cancellationToken = default)
{
try
// Refresh _account from PCA cache each call — interactive flows on a
// sibling token provider populate the cache, and we want the next
// request on this provider to use that account silently.
if (_account is null)
{
var silent = _pca.AcquireTokenSilent(_scopes, _account);
if (_tenantId is not null) silent = silent.WithTenantId(_tenantId);
var result = await silent.ExecuteAsync(cancellationToken);
return result.AccessToken;
var accounts = await _pca.GetAccountsAsync();
_account = accounts.FirstOrDefault();
}
catch (MsalUiRequiredException)
if (_account is not null)
{
var interactive = _pca.AcquireTokenInteractive(_scopes);
if (_tenantId is not null) interactive = interactive.WithTenantId(_tenantId);
var result = await interactive.ExecuteAsync(cancellationToken);
return result.AccessToken;
try
{
var silent = _pca.AcquireTokenSilent(_scopes, _account);
if (_tenantId is not null) silent = silent.WithTenantId(_tenantId);
var result = await silent.ExecuteAsync(cancellationToken);
return result.AccessToken;
}
catch (MsalUiRequiredException)
{
// fall through to interactive
}
}
var interactive = _pca.AcquireTokenInteractive(_scopes);
if (_tenantId is not null) interactive = interactive.WithTenantId(_tenantId);
var interactiveResult = await interactive.ExecuteAsync(cancellationToken);
// Cache the account so subsequent calls on this provider go silent.
_account = interactiveResult.Account;
return interactiveResult.AccessToken;
}
}