Skip to content

Commit 68bcc34

Browse files
committed
msauth: workaround MSAL.NET issue with MSA-PT accounts
When we have a Microsoft Account (MSA) in the cache and attempt to do a silent authentication, if we're an MSA-PT app we need to specify the special MSA transfer tenant ID to make sure we get the a token silently, correctly. See the issue [1] in the MSAL repo for more information. [1] AzureAD/microsoft-authentication-library-for-dotnet#3077
1 parent 99dea8e commit 68bcc34

File tree

2 files changed

+37
-6
lines changed

2 files changed

+37
-6
lines changed

src/shared/Core/Authentication/MicrosoftAuthentication.cs

Lines changed: 26 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using System;
22
using System.Collections.Generic;
33
using System.IO;
4+
using System.Linq;
45
using System.Net.Http;
56
using System.Threading.Tasks;
67
using GitCredentialManager.Interop.Windows.Native;
@@ -78,7 +79,7 @@ public async Task<IMicrosoftAuthenticationResult> GetTokenAsync(
7879
bool hasExistingUser = !string.IsNullOrWhiteSpace(userName);
7980
if (hasExistingUser)
8081
{
81-
result = await GetAccessTokenSilentlyAsync(app, scopes, userName);
82+
result = await GetAccessTokenSilentlyAsync(app, scopes, userName, msaPt);
8283
}
8384

8485
//
@@ -116,7 +117,7 @@ public async Task<IMicrosoftAuthenticationResult> GetTokenAsync(
116117
// account then the user may become stuck in a loop of authentication failures.
117118
if (!hasExistingUser && Context.Settings.UseMsAuthDefaultAccount)
118119
{
119-
result = await GetAccessTokenSilentlyAsync(app, scopes, null);
120+
result = await GetAccessTokenSilentlyAsync(app, scopes, null, msaPt);
120121

121122
if (result is null || !await UseDefaultAccountAsync(result.Account.Username))
122123
{
@@ -281,7 +282,8 @@ internal MicrosoftAuthenticationFlowType GetFlowType()
281282
/// <summary>
282283
/// Obtain an access token without showing UI or prompts.
283284
/// </summary>
284-
private async Task<AuthenticationResult> GetAccessTokenSilentlyAsync(IPublicClientApplication app, string[] scopes, string userName)
285+
private async Task<AuthenticationResult> GetAccessTokenSilentlyAsync(
286+
IPublicClientApplication app, string[] scopes, string userName, bool msaPt)
285287
{
286288
try
287289
{
@@ -295,9 +297,27 @@ private async Task<AuthenticationResult> GetAccessTokenSilentlyAsync(IPublicClie
295297
{
296298
Context.Trace.WriteLine($"Attempting to acquire token silently for user '{userName}'...");
297299

298-
// We can either call `app.GetAccountsAsync` and filter through the IAccount objects for the instance with the correct user name,
299-
// or we can just pass the user name string we have as the `loginHint` and let MSAL do exactly that for us instead!
300-
return await app.AcquireTokenSilent(scopes, loginHint: userName).ExecuteAsync();
300+
// Enumerate all accounts and find the one matching the user name
301+
IEnumerable<IAccount> accounts = await app.GetAccountsAsync();
302+
IAccount account = accounts.FirstOrDefault(x => StringComparer.OrdinalIgnoreCase.Equals(x.Username, userName));
303+
if (account is null)
304+
{
305+
Context.Trace.WriteLine($"No cached account found for user '{userName}'...");
306+
return null;
307+
}
308+
309+
var atsBuilder = app.AcquireTokenSilent(scopes, account);
310+
311+
// Is we are operating with an MSA passthrough app we need to ensure that we target the
312+
// special MSA 'transfer' tenant explicitly. This is a workaround for MSAL issue:
313+
// https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/3077
314+
if (msaPt && Guid.TryParse(account.HomeAccountId.TenantId, out Guid homeTenantId) &&
315+
homeTenantId == Constants.MsaHomeTenantId)
316+
{
317+
atsBuilder = atsBuilder.WithTenantId(Constants.MsaTransferTenantId.ToString("D"));
318+
}
319+
320+
return await atsBuilder.ExecuteAsync();
301321
}
302322
}
303323
catch (MsalUiRequiredException)

src/shared/Core/Constants.cs

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,17 @@ public static class Constants
1818

1919
public static readonly Guid DevBoxPartnerId = new("e3171dd9-9a5f-e5be-b36c-cc7c4f3f3bcf");
2020

21+
/// <summary>
22+
/// Home tenant ID for Microsoft Accounts (MSA).
23+
/// </summary>
24+
public static readonly Guid MsaHomeTenantId = new("9188040d-6c67-4c5b-b112-36a304b66dad");
25+
26+
/// <summary>
27+
/// Special tenant ID for transferring between Microsoft Account (MSA) native tokens
28+
/// and AAD tokens. Only required for MSA-Passthrough applications.
29+
/// </summary>
30+
public static readonly Guid MsaTransferTenantId = new("f8cdef31-a31e-4b4a-93e4-5f571e91255a");
31+
2132
public static class CredentialStoreNames
2233
{
2334
public const string WindowsCredentialManager = "wincredman";

0 commit comments

Comments
 (0)