From d5839af196f301cfe8ce43b9728bdcd7af18e4a5 Mon Sep 17 00:00:00 2001 From: trwalke Date: Fri, 10 Oct 2025 00:03:16 -0700 Subject: [PATCH 01/14] Updating MSAL to send client info = 2 cor client credential flow --- .../Requests/ClientCredentialRequest.cs | 3 +- .../ClientCredentialWithCertTest.cs | 38 +++++++++++++++++++ 2 files changed, 40 insertions(+), 1 deletion(-) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs index 48662d8f42..e7c08f0fc8 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs @@ -335,7 +335,8 @@ private Dictionary GetBodyParameters() var dict = new Dictionary { [OAuth2Parameter.GrantType] = OAuth2GrantType.ClientCredentials, - [OAuth2Parameter.Scope] = AuthenticationRequestParameters.Scope.AsSingleString() + [OAuth2Parameter.Scope] = AuthenticationRequestParameters.Scope.AsSingleString(), + [OAuth2Parameter.ClientInfo] = "2" }; return dict; diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs index f3b34406fa..fb3a0e58fe 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ClientCredentialWithCertTest.cs @@ -23,6 +23,7 @@ using static Microsoft.Identity.Client.Internal.JsonWebToken; using Microsoft.Identity.Client.RP; using Microsoft.Identity.Client.Http; +using Microsoft.Identity.Client.OAuth2; namespace Microsoft.Identity.Test.Unit { @@ -1019,6 +1020,43 @@ public void EnsureNullCertDoesNotSetSerialNumberTestAsync() } } + [TestMethod] + public async Task AcquireTokenForClient_ShouldSendClientInfoParameter_WithValueTwo_Async() + { + // Arrange + using (var httpManager = new MockHttpManager()) + { + httpManager.AddInstanceDiscoveryMockHandler(); + + // Set up the expected POST data to include client_info = "2" + var expectedPostData = new Dictionary + { + [OAuth2Parameter.GrantType] = OAuth2GrantType.ClientCredentials, + [OAuth2Parameter.Scope] = TestConstants.s_scope.AsSingleString(), + [OAuth2Parameter.ClientInfo] = "2" + }; + + var handler = httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage( + expectedPostData: expectedPostData); + + var app = ConfidentialClientApplicationBuilder + .Create(TestConstants.ClientId) + .WithClientSecret(TestConstants.ClientSecret) + .WithAuthority(TestConstants.AuthorityCommonTenant) + .WithHttpManager(httpManager) + .BuildConcrete(); + + // Act + var result = await app.AcquireTokenForClient(TestConstants.s_scope) + .ExecuteAsync() + .ConfigureAwait(false); + + // Assert + Assert.IsNotNull(result); + Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource); + } + } + private void BeforeCacheAccess(TokenCacheNotificationArgs args) { args.TokenCache.DeserializeMsalV3(_serializedCache); From 040a22f59ebd5bc7e00f5b74ef9bca9c0a7338c6 Mon Sep 17 00:00:00 2001 From: trwalke Date: Tue, 14 Oct 2025 21:36:50 -0700 Subject: [PATCH 02/14] Updating xmsacb handling. added test --- .../AuthenticationResult.cs | 9 ++- .../Cache/Items/MsalAccessTokenCacheItem.cs | 9 ++- .../Cache/StorageJsonKeys.cs | 1 + .../Internal/ClientInfo.cs | 4 ++ .../Internal/ClientInfoClaim.cs | 1 + .../Requests/ClientCredentialRequest.cs | 3 +- .../Internal/Requests/RequestBase.cs | 14 ++--- .../PublicApi/net462/PublicAPI.Unshipped.txt | 2 + .../PublicApi/net472/PublicAPI.Unshipped.txt | 2 + .../net8.0-android/PublicAPI.Unshipped.txt | 2 + .../net8.0-ios/PublicAPI.Unshipped.txt | 2 + .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 2 + .../netstandard2.0/PublicAPI.Unshipped.txt | 2 + .../Core/Mocks/MockHelpers.cs | 23 ++++++- .../Core/Mocks/MockHttpManagerExtensions.cs | 10 +-- .../ConfidentialClientApplicationTests.cs | 61 +++++++++++++++++++ 16 files changed, 132 insertions(+), 15 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs index 570fa706b4..9182a5c22a 100644 --- a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs +++ b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs @@ -135,7 +135,8 @@ internal AuthenticationResult( ApiEvent apiEvent, Account account, string spaAuthCode, - IReadOnlyDictionary additionalResponseParameters) + IReadOnlyDictionary additionalResponseParameters, + List acbAuthN = null) { _authenticationScheme = authenticationScheme ?? throw new ArgumentNullException(nameof(authenticationScheme)); @@ -198,6 +199,7 @@ internal AuthenticationResult( AuthenticationResultMetadata.DurationCreatingExtendedTokenInUs = measuredResultDuration.Microseconds; AuthenticationResultMetadata.TelemetryTokenType = authenticationScheme.TelemetryTokenType; + AcbAuthN = acbAuthN; } //Default constructor for testing @@ -318,6 +320,11 @@ internal AuthenticationResult() { } /// public AuthenticationResultMetadata AuthenticationResultMetadata { get; set; } + /// + /// Represents the xms_acb claim in AuthN Tokens acquired from ESTS + /// + public List AcbAuthN { get; set; } + /// /// Creates the content for an HTTP authorization header from this authentication result, so /// that you can call a protected API diff --git a/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs b/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs index 9e2e25294d..282ff8deeb 100644 --- a/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs +++ b/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs @@ -37,7 +37,8 @@ internal MsalAccessTokenCacheItem( string keyId = null, string oboCacheKey = null, IEnumerable persistedCacheParameters = null, - SortedList cacheKeyComponents = null) + SortedList cacheKeyComponents = null, + List acbAuthN = null) : this( scopes: ScopeHelper.OrderScopesAlphabetically(response.Scope), // order scopes to avoid cache duplication. This is not in the hot path. cachedAt: DateTimeOffset.UtcNow, @@ -54,6 +55,7 @@ internal MsalAccessTokenCacheItem( RawClientInfo = response.ClientInfo; HomeAccountId = homeAccountId; OboCacheKey = oboCacheKey; + AcbAuthN = acbAuthN; InitializeAdditionalCacheKeyComponents(cacheKeyComponents); #if !MOBILE @@ -288,6 +290,8 @@ internal string TenantId /// internal IDictionary PersistedCacheParameters { get; private set; } + internal List AcbAuthN { get; private set; } + private Lazy iOSCacheKeyLazy; public IiOSKey iOSCacheKey => iOSCacheKeyLazy.Value; @@ -322,6 +326,7 @@ internal static MsalAccessTokenCacheItem FromJObject(JObject j) string scopes = JsonHelper.ExtractExistingOrEmptyString(j, StorageJsonKeys.Target); var additionalCacheKeyComponents = JsonHelper.ExtractInnerJsonAsDictionary(j, StorageJsonKeys.CacheExtensions); var persistedCacheParameters = JsonHelper.ExtractInnerJsonAsDictionary(j, StorageJsonKeys.PersistedCacheParameters); + string acbAuthN = JsonHelper.ExtractExistingOrDefault(j, StorageJsonKeys.AcbAuthN); var item = new MsalAccessTokenCacheItem( scopes: scopes, @@ -341,6 +346,7 @@ internal static MsalAccessTokenCacheItem FromJObject(JObject j) item.PersistedCacheParameters = persistedCacheParameters; item.OboCacheKey = oboCacheKey; + item.AcbAuthN = string.IsNullOrWhiteSpace(acbAuthN) ? null : acbAuthN.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); item.PopulateFieldsFromJObject(j); item.InitCacheKey(); @@ -365,6 +371,7 @@ internal override JObject ToJObject() json, StorageJsonKeys.RefreshOn, RefreshOn.HasValue ? DateTimeHelpers.DateTimeToUnixTimestamp(RefreshOn.Value) : null); + SetItemIfValueNotNull(json, StorageJsonKeys.AcbAuthN, AcbAuthN != null && AcbAuthN.Any() ? string.Join(" ", AcbAuthN) : null); // previous versions of MSAL used "ext_expires_on" instead of the correct "extended_expires_on". // this is here for back compatibility diff --git a/src/client/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs b/src/client/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs index 0faf82843c..32bb244bac 100644 --- a/src/client/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs +++ b/src/client/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs @@ -44,5 +44,6 @@ internal static class StorageJsonKeys public const string CacheExtensions = "ext"; public const string PersistedCacheParameters = "persisted_cache_parameters"; + public const string AcbAuthN = "xms_acb"; } } diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs b/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs index 6177fe6632..a7e108e7be 100644 --- a/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs +++ b/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs @@ -2,6 +2,7 @@ // Licensed under the MIT License. using System; +using System.Collections.Generic; using System.Globalization; using Microsoft.Identity.Client.Utils; #if SUPPORTS_SYSTEM_TEXT_JSON @@ -24,6 +25,9 @@ internal class ClientInfo [JsonProperty(ClientInfoClaim.UniqueTenantIdentifier)] public string UniqueTenantIdentifier { get; set; } + [JsonProperty(ClientInfoClaim.AcbAuthN)] + public List AcbAuthN { get; set; } + public static ClientInfo CreateFromJson(string clientInfo) { if (string.IsNullOrEmpty(clientInfo)) diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientInfoClaim.cs b/src/client/Microsoft.Identity.Client/Internal/ClientInfoClaim.cs index 6168140587..4eebb51864 100644 --- a/src/client/Microsoft.Identity.Client/Internal/ClientInfoClaim.cs +++ b/src/client/Microsoft.Identity.Client/Internal/ClientInfoClaim.cs @@ -7,5 +7,6 @@ internal static class ClientInfoClaim { public const string UniqueIdentifier = "uid"; public const string UniqueTenantIdentifier = "utid"; + public const string AcbAuthN = "xms_acb"; } } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs index e7c08f0fc8..7c5971bbcb 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs @@ -309,7 +309,8 @@ private AuthenticationResult CreateAuthenticationResultFromCache(MsalAccessToken AuthenticationRequestParameters.RequestContext.ApiEvent, account: null, spaAuthCode: null, - additionalResponseParameters: null); + additionalResponseParameters: null, + acbAuthN: cachedAccessTokenItem.AcbAuthN); return authResult; } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 0ac5bd3627..a4be35eb74 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -317,20 +317,19 @@ protected async Task CacheTokenResponseAndCreateAuthentica // developer passed in user object. AuthenticationRequestParameters.RequestContext.Logger.Info("Checking client info returned from the server.."); - ClientInfo fromServer = null; + ClientInfo clientInfoFromServer = null; - if (!AuthenticationRequestParameters.IsClientCredentialRequest && - AuthenticationRequestParameters.ApiId != ApiEvent.ApiIds.AcquireTokenForSystemAssignedManagedIdentity && + if (AuthenticationRequestParameters.ApiId != ApiEvent.ApiIds.AcquireTokenForSystemAssignedManagedIdentity && AuthenticationRequestParameters.ApiId != ApiEvent.ApiIds.AcquireTokenForUserAssignedManagedIdentity && AuthenticationRequestParameters.ApiId != ApiEvent.ApiIds.AcquireTokenByRefreshToken && AuthenticationRequestParameters.AuthorityInfo.AuthorityType != AuthorityType.Adfs && !(msalTokenResponse.ClientInfo is null)) { - //client_info is not returned from client credential and managed identity flows because there is no user present. - fromServer = ClientInfo.CreateFromJson(msalTokenResponse.ClientInfo); + //client_info is not returned from managed identity flows because there is no user present. + clientInfoFromServer = ClientInfo.CreateFromJson(msalTokenResponse.ClientInfo); } - ValidateAccountIdentifiers(fromServer); + ValidateAccountIdentifiers(clientInfoFromServer); AuthenticationRequestParameters.RequestContext.Logger.Info("Saving token response to cache.."); @@ -348,7 +347,8 @@ protected async Task CacheTokenResponseAndCreateAuthentica AuthenticationRequestParameters.RequestContext.ApiEvent, account, msalTokenResponse.SpaAuthCode, - msalTokenResponse.CreateExtensionDataStringMap()); + msalTokenResponse.CreateExtensionDataStringMap(), + acbAuthN: clientInfoFromServer.AcbAuthN); } protected virtual void ValidateAccountIdentifiers(ClientInfo fromServer) diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index e69de29bb2..b6bb0a4aae 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index e69de29bb2..b6bb0a4aae 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index e69de29bb2..b6bb0a4aae 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index e69de29bb2..b6bb0a4aae 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index e69de29bb2..b6bb0a4aae 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index e69de29bb2..b6bb0a4aae 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -0,0 +1,2 @@ +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List +Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs index 7050ae13e5..f51f0db997 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs @@ -170,8 +170,18 @@ public static string GetMsiImdsErrorResponse() "\"correlation_id\":\"77145480-bc5a-4ebe-ae4d-e4a8b7d727cf\",\"error_uri\":\"https://westus2.login.microsoft.com/error?code=500011\"}"; } - public static string CreateClientInfo(string uid = TestConstants.Uid, string utid = TestConstants.Utid) + public static string CreateClientInfo(string uid = TestConstants.Uid, string utid = TestConstants.Utid, bool addXms_acb = false) { + if (addXms_acb) + { + if (!string.IsNullOrEmpty(uid)) + { + return Base64UrlHelpers.Encode("{\"uid\":\"" + uid + "\",\"utid\":\"" + utid + "\",\"xms_acb\":[\"value1\",\"value2\"]}"); + } + + return Base64UrlHelpers.Encode("{\"xms_acb\":[\"value1\",\"value2\"]}"); + } + return Base64UrlHelpers.Encode("{\"uid\":\"" + uid + "\",\"utid\":\"" + utid + "\"}"); } @@ -354,6 +364,17 @@ public static HttpResponseMessage CreateSuccessfulClientCredentialTokenResponseM "{\"token_type\":\"" + tokenType + "\",\"expires_in\":\"" + expiry + "\",\"access_token\":\"" + token + "\",\"additional_param1\":\"value1\",\"additional_param2\":\"value2\",\"additional_param3\":\"value3\"}"); } + public static HttpResponseMessage CreateSuccessfulClientCredentialTokenResponseWithClientInfoMessage( + string token = "header.payload.signature", + string expiry = "3599", + string tokenType = "Bearer", + bool addXms_acb = false + ) + { + return CreateSuccessResponseMessage( + "{\"token_type\":\"" + tokenType + "\",\"expires_in\":\"" + expiry + "\",\"access_token\":\"" + token + "\",\"additional_param1\":\"value1\",\"additional_param2\":\"value2\",\"additional_param3\":\"value3\"\"client_info\":\"" + CreateClientInfo(null, null, addXms_acb) + "\"}"); + } + public static HttpResponseMessage CreateSuccessfulClientCredentialTokenResponseWithAdditionalParamsMessage( string token = "header.payload.signature", string expiry = "3599", diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs index 565ca72e68..867e111230 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs @@ -183,18 +183,20 @@ public static void AddMockHandlerContentNotFound(this MockHttpManager httpManage } public static MockHttpMessageHandler AddMockHandlerSuccessfulClientCredentialTokenResponseMessage( - this MockHttpManager httpManager, - string token = "header.payload.signature", + this MockHttpManager httpManager, + string token = "header.payload.signature", string expiresIn = "3599", string tokenType = "Bearer", IList unexpectedHttpHeaders = null, - Dictionary expectedPostData = null + Dictionary expectedPostData = null, + bool addClientInfo = false ) { var handler = new MockHttpMessageHandler() { ExpectedMethod = HttpMethod.Post, - ResponseMessage = MockHelpers.CreateSuccessfulClientCredentialTokenResponseMessage(token, expiresIn, tokenType), + ResponseMessage = addClientInfo? MockHelpers.CreateSuccessfulClientCredentialTokenResponseWithClientInfoMessage(token, expiresIn, tokenType, addClientInfo) + : MockHelpers.CreateSuccessfulClientCredentialTokenResponseMessage(token, expiresIn, tokenType), UnexpectedRequestHeaders = unexpectedHttpHeaders, ExpectedPostData = expectedPostData }; diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs index 8ccf3568df..daa101313c 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs @@ -2320,6 +2320,67 @@ public async Task AcquireTokenForClient_WithClaims_And_MismatchedHash_UsesCache_ } } + [TestMethod] + public async Task ConfidentialClient_acquireTokenForClient_ReturnsXmsAcbTestAsync() + { + using (var httpManager = new MockHttpManager()) + { + httpManager.AddInstanceDiscoveryMockHandler(); + + var app = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) + .WithAuthority(new Uri(ClientApplicationBase.DefaultAuthority), true) + .WithRedirectUri(TestConstants.RedirectUri) + .WithClientSecret(TestConstants.ClientSecret) + .WithHttpManager(httpManager) + .BuildConcrete(); + + httpManager.AddMockHandlerSuccessfulClientCredentialTokenResponseMessage(addClientInfo: true); + var appCacheAccess = app.AppTokenCache.RecordAccess(); + var userCacheAccess = app.UserTokenCache.RecordAccess(); + + var result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray()).ExecuteAsync(CancellationToken.None).ConfigureAwait(false); + Assert.IsNotNull(result); + Assert.AreEqual("header.payload.signature", result.AccessToken); + Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); + Assert.IsNotNull(result.AcbAuthN); + Assert.IsTrue(result.AcbAuthN.Count == 2); + Assert.AreEqual("value1", result.AcbAuthN[0]); + Assert.AreEqual("value2", result.AcbAuthN[1]); + + // make sure user token cache is empty + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count); + + // check app token cache count to be 1 + Assert.AreEqual(1, app.AppTokenCacheInternal.Accessor.GetAllAccessTokens().Count); + Assert.AreEqual(0, app.AppTokenCacheInternal.Accessor.GetAllRefreshTokens().Count); + + appCacheAccess.AssertAccessCounts(1, 1); + userCacheAccess.AssertAccessCounts(0, 0); + + // call AcquireTokenForClientAsync again to get result back from the cache + result = await app.AcquireTokenForClient(TestConstants.s_scope.ToArray()).ExecuteAsync(CancellationToken.None).ConfigureAwait(false); + Assert.IsNotNull(result); + Assert.AreEqual("header.payload.signature", result.AccessToken); + Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); + Assert.IsNotNull(result.AcbAuthN); + Assert.IsTrue(result.AcbAuthN.Count == 2); + Assert.AreEqual("value1", result.AcbAuthN[0]); + Assert.AreEqual("value2", result.AcbAuthN[1]); + + // make sure user token cache is empty + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); + Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count); + + // check app token cache count to be 1 + Assert.AreEqual(1, app.AppTokenCacheInternal.Accessor.GetAllAccessTokens().Count); + Assert.AreEqual(0, app.AppTokenCacheInternal.Accessor.GetAllRefreshTokens().Count); + + appCacheAccess.AssertAccessCounts(2, 1); + userCacheAccess.AssertAccessCounts(0, 0); + } + } + private static string ComputeSHA256Hex(string token) { var cryptoMgr = new CommonCryptographyManager(); From be3dde18260ef0abb7a4a3099e2cd8429864358a Mon Sep 17 00:00:00 2001 From: trwalke Date: Wed, 15 Oct 2025 02:23:23 -0700 Subject: [PATCH 03/14] Fixing error --- tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs index f51f0db997..7a73f4816f 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs @@ -372,7 +372,7 @@ public static HttpResponseMessage CreateSuccessfulClientCredentialTokenResponseW ) { return CreateSuccessResponseMessage( - "{\"token_type\":\"" + tokenType + "\",\"expires_in\":\"" + expiry + "\",\"access_token\":\"" + token + "\",\"additional_param1\":\"value1\",\"additional_param2\":\"value2\",\"additional_param3\":\"value3\"\"client_info\":\"" + CreateClientInfo(null, null, addXms_acb) + "\"}"); + "{\"token_type\":\"" + tokenType + "\",\"expires_in\":\"" + expiry + "\",\"access_token\":\"" + token + "\",\"additional_param1\":\"value1\",\"additional_param2\":\"value2\",\"additional_param3\":\"value3\",\"client_info\":\"" + CreateClientInfo(null, null, addXms_acb) + "\"}"); } public static HttpResponseMessage CreateSuccessfulClientCredentialTokenResponseWithAdditionalParamsMessage( From bcab030778409d8c2ea92063dff9ade612dbe097 Mon Sep 17 00:00:00 2001 From: trwalke Date: Thu, 16 Oct 2025 22:02:41 -0700 Subject: [PATCH 04/14] Updating xms_acb implementation --- .../AuthenticationResult.cs | 43 ++++++++++++++----- .../Cache/Items/MsalAccessTokenCacheItem.cs | 8 ++-- .../Internal/ClientInfo.cs | 4 +- .../Internal/Requests/RequestBase.cs | 6 ++- .../OAuth2/MsalTokenResponse.cs | 2 + .../PublicApi/net462/PublicAPI.Unshipped.txt | 2 - .../PublicApi/net472/PublicAPI.Unshipped.txt | 2 - .../net8.0-android/PublicAPI.Unshipped.txt | 2 - .../net8.0-ios/PublicAPI.Unshipped.txt | 2 - .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 2 - .../netstandard2.0/PublicAPI.Unshipped.txt | 2 - .../TokenCache.ITokenCacheInternal.cs | 4 +- .../ConfidentialClientApplicationTests.cs | 16 +++---- 13 files changed, 57 insertions(+), 38 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs index 9182a5c22a..86c672b440 100644 --- a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs +++ b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs @@ -13,6 +13,7 @@ using Microsoft.Identity.Client.TelemetryCore.Internal.Events; using Microsoft.Identity.Client.Utils; using System.Security.Cryptography.X509Certificates; +using System.Linq; namespace Microsoft.Identity.Client { @@ -136,7 +137,7 @@ internal AuthenticationResult( Account account, string spaAuthCode, IReadOnlyDictionary additionalResponseParameters, - List acbAuthN = null) + string acbAuthN = null) { _authenticationScheme = authenticationScheme ?? throw new ArgumentNullException(nameof(authenticationScheme)); @@ -168,9 +169,37 @@ internal AuthenticationResult( CorrelationId = correlationID; ApiEvent = apiEvent; AuthenticationResultMetadata = new AuthenticationResultMetadata(tokenSource); - AdditionalResponseParameters = msalAccessTokenCacheItem?.PersistedCacheParameters?.Count > 0 ? - (IReadOnlyDictionary)msalAccessTokenCacheItem.PersistedCacheParameters : - additionalResponseParameters; + + if (!string.IsNullOrEmpty(acbAuthN)) + { + if (msalAccessTokenCacheItem.PersistedCacheParameters?.Count > 0) + { + msalAccessTokenCacheItem.PersistedCacheParameters.Add("xms_acb", acbAuthN); + AdditionalResponseParameters = (IReadOnlyDictionary)msalAccessTokenCacheItem.PersistedCacheParameters; + } + else + { + if (additionalResponseParameters == null) + { + additionalResponseParameters = new Dictionary { { "xms_acb", acbAuthN } }; + } + else + { + Dictionary tempParams = additionalResponseParameters.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); + tempParams.Add("xms_acb", acbAuthN); + additionalResponseParameters = tempParams; + } + + AdditionalResponseParameters = additionalResponseParameters; + } + } + else + { + AdditionalResponseParameters = msalAccessTokenCacheItem?.PersistedCacheParameters?.Count > 0 ? + (IReadOnlyDictionary)msalAccessTokenCacheItem.PersistedCacheParameters : + additionalResponseParameters; + } + if (msalAccessTokenCacheItem != null) { ExpiresOn = msalAccessTokenCacheItem.ExpiresOn; @@ -199,7 +228,6 @@ internal AuthenticationResult( AuthenticationResultMetadata.DurationCreatingExtendedTokenInUs = measuredResultDuration.Microseconds; AuthenticationResultMetadata.TelemetryTokenType = authenticationScheme.TelemetryTokenType; - AcbAuthN = acbAuthN; } //Default constructor for testing @@ -320,11 +348,6 @@ internal AuthenticationResult() { } /// public AuthenticationResultMetadata AuthenticationResultMetadata { get; set; } - /// - /// Represents the xms_acb claim in AuthN Tokens acquired from ESTS - /// - public List AcbAuthN { get; set; } - /// /// Creates the content for an HTTP authorization header from this authentication result, so /// that you can call a protected API diff --git a/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs b/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs index 282ff8deeb..c2358a136f 100644 --- a/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs +++ b/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs @@ -38,7 +38,7 @@ internal MsalAccessTokenCacheItem( string oboCacheKey = null, IEnumerable persistedCacheParameters = null, SortedList cacheKeyComponents = null, - List acbAuthN = null) + string acbAuthN = null) : this( scopes: ScopeHelper.OrderScopesAlphabetically(response.Scope), // order scopes to avoid cache duplication. This is not in the hot path. cachedAt: DateTimeOffset.UtcNow, @@ -290,7 +290,7 @@ internal string TenantId /// internal IDictionary PersistedCacheParameters { get; private set; } - internal List AcbAuthN { get; private set; } + internal string AcbAuthN { get; private set; } private Lazy iOSCacheKeyLazy; public IiOSKey iOSCacheKey => iOSCacheKeyLazy.Value; @@ -346,7 +346,7 @@ internal static MsalAccessTokenCacheItem FromJObject(JObject j) item.PersistedCacheParameters = persistedCacheParameters; item.OboCacheKey = oboCacheKey; - item.AcbAuthN = string.IsNullOrWhiteSpace(acbAuthN) ? null : acbAuthN.Split(new char[] { ' ' }, StringSplitOptions.RemoveEmptyEntries).ToList(); + item.AcbAuthN = acbAuthN; item.PopulateFieldsFromJObject(j); item.InitCacheKey(); @@ -371,7 +371,7 @@ internal override JObject ToJObject() json, StorageJsonKeys.RefreshOn, RefreshOn.HasValue ? DateTimeHelpers.DateTimeToUnixTimestamp(RefreshOn.Value) : null); - SetItemIfValueNotNull(json, StorageJsonKeys.AcbAuthN, AcbAuthN != null && AcbAuthN.Any() ? string.Join(" ", AcbAuthN) : null); + SetItemIfValueNotNull(json, StorageJsonKeys.AcbAuthN, AcbAuthN); // previous versions of MSAL used "ext_expires_on" instead of the correct "extended_expires_on". // this is here for back compatibility diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs b/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs index a7e108e7be..5b504fb3ca 100644 --- a/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs +++ b/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs @@ -26,7 +26,9 @@ internal class ClientInfo public string UniqueTenantIdentifier { get; set; } [JsonProperty(ClientInfoClaim.AcbAuthN)] - public List AcbAuthN { get; set; } + private IEnumerable AcbAuthNRaw { get; set; } + + public string AcbAuthN => string.Join(" ", AcbAuthNRaw); public static ClientInfo CreateFromJson(string clientInfo) { diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index a4be35eb74..53ddcc16b1 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -327,10 +327,10 @@ protected async Task CacheTokenResponseAndCreateAuthentica { //client_info is not returned from managed identity flows because there is no user present. clientInfoFromServer = ClientInfo.CreateFromJson(msalTokenResponse.ClientInfo); + ValidateAccountIdentifiers(clientInfoFromServer); } - ValidateAccountIdentifiers(clientInfoFromServer); - + msalTokenResponse.AcbAuthN = clientInfoFromServer.AcbAuthN; AuthenticationRequestParameters.RequestContext.Logger.Info("Saving token response to cache.."); var tuple = await CacheManager.SaveTokenResponseAsync(msalTokenResponse).ConfigureAwait(false); @@ -338,6 +338,8 @@ protected async Task CacheTokenResponseAndCreateAuthentica var idtItem = tuple.Item2; Account account = tuple.Item3; + //TODO Get client info from response and use it to + return new AuthenticationResult( atItem, idtItem, diff --git a/src/client/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs b/src/client/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs index 2898c073aa..8e16e5f257 100644 --- a/src/client/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs +++ b/src/client/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs @@ -183,6 +183,8 @@ public IReadOnlyDictionary CreateExtensionDataStringMap() public HttpResponse HttpResponse { get; set; } + public string AcbAuthN { get; set; } + internal static MsalTokenResponse CreateFromiOSBrokerResponse(Dictionary responseDictionary) { if (responseDictionary.TryGetValue(BrokerResponseConst.BrokerErrorCode, out string errorCode)) diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt index b6bb0a4aae..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -1,2 +0,0 @@ -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt index b6bb0a4aae..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -1,2 +0,0 @@ -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt index b6bb0a4aae..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-android/PublicAPI.Unshipped.txt @@ -1,2 +0,0 @@ -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt index b6bb0a4aae..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0-ios/PublicAPI.Unshipped.txt @@ -1,2 +0,0 @@ -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt index b6bb0a4aae..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net8.0/PublicAPI.Unshipped.txt @@ -1,2 +0,0 @@ -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt index b6bb0a4aae..e69de29bb2 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/netstandard2.0/PublicAPI.Unshipped.txt @@ -1,2 +0,0 @@ -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.get -> System.Collections.Generic.List -Microsoft.Identity.Client.AuthenticationResult.AcbAuthN.set -> void diff --git a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs index f0fefb153f..76a5aef423 100644 --- a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs +++ b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs @@ -82,7 +82,9 @@ async Task> IToke requestParams.AuthenticationScheme.KeyId, CacheKeyFactory.GetOboKey(requestParams.LongRunningOboCacheKey, requestParams.UserAssertion), requestParams.PersistedCacheParameters, - requestParams.CacheKeyComponents); + requestParams.CacheKeyComponents, + response.AcbAuthN); + //TODO need client info here } if (!string.IsNullOrEmpty(response.RefreshToken)) diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs index daa101313c..99a9c37c5b 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs @@ -2342,10 +2342,8 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsXmsAcbTestAsyn Assert.IsNotNull(result); Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); - Assert.IsNotNull(result.AcbAuthN); - Assert.IsTrue(result.AcbAuthN.Count == 2); - Assert.AreEqual("value1", result.AcbAuthN[0]); - Assert.AreEqual("value2", result.AcbAuthN[1]); + Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("xms_acb")); + Assert.AreEqual("value1 value2", result.AdditionalResponseParameters["xms_acb"]); // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); @@ -2363,10 +2361,12 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsXmsAcbTestAsyn Assert.IsNotNull(result); Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); - Assert.IsNotNull(result.AcbAuthN); - Assert.IsTrue(result.AcbAuthN.Count == 2); - Assert.AreEqual("value1", result.AcbAuthN[0]); - Assert.AreEqual("value2", result.AcbAuthN[1]); + Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("xms_acb")); + + //Assert.IsNotNull(result.AcbAuthN); + //Assert.IsTrue(result.AcbAuthN.Count == 2); + //Assert.AreEqual("value1", result.AcbAuthN[0]); + //Assert.AreEqual("value2", result.AcbAuthN[1]); // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); From 7e4c63fb58ff48be9fb4b7cc6286997ddfc45ab6 Mon Sep 17 00:00:00 2001 From: trwalke Date: Mon, 20 Oct 2025 02:25:00 -0700 Subject: [PATCH 05/14] Updating tests --- .../PublicApiTests/ConfidentialClientApplicationTests.cs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs index 99a9c37c5b..df9ca7b07c 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs @@ -2362,11 +2362,7 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsXmsAcbTestAsyn Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("xms_acb")); - - //Assert.IsNotNull(result.AcbAuthN); - //Assert.IsTrue(result.AcbAuthN.Count == 2); - //Assert.AreEqual("value1", result.AcbAuthN[0]); - //Assert.AreEqual("value2", result.AcbAuthN[1]); + Assert.AreEqual("value1 value2", result.AdditionalResponseParameters["xms_acb"]); // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); From 55f1afde88dc027fe461a49c9c8d383079ab4d5e Mon Sep 17 00:00:00 2001 From: trwalke Date: Tue, 21 Oct 2025 01:11:58 -0700 Subject: [PATCH 06/14] Making implementation more generic --- .../AuthenticationResult.cs | 35 +++---------------- .../Cache/Items/MsalAccessTokenCacheItem.cs | 26 ++++++++++---- .../Cache/StorageJsonKeys.cs | 1 - .../Internal/ClientInfo.cs | 35 +++++++++++++++---- .../Internal/ClientInfoClaim.cs | 1 - .../Requests/ClientCredentialRequest.cs | 3 +- .../Internal/Requests/RequestBase.cs | 6 ++-- .../OAuth2/MsalTokenResponse.cs | 2 -- .../TokenCache.ITokenCacheInternal.cs | 3 +- 9 files changed, 57 insertions(+), 55 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs index 86c672b440..f9cdf6b5df 100644 --- a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs +++ b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs @@ -136,8 +136,7 @@ internal AuthenticationResult( ApiEvent apiEvent, Account account, string spaAuthCode, - IReadOnlyDictionary additionalResponseParameters, - string acbAuthN = null) + IReadOnlyDictionary additionalResponseParameters) { _authenticationScheme = authenticationScheme ?? throw new ArgumentNullException(nameof(authenticationScheme)); @@ -170,35 +169,9 @@ internal AuthenticationResult( ApiEvent = apiEvent; AuthenticationResultMetadata = new AuthenticationResultMetadata(tokenSource); - if (!string.IsNullOrEmpty(acbAuthN)) - { - if (msalAccessTokenCacheItem.PersistedCacheParameters?.Count > 0) - { - msalAccessTokenCacheItem.PersistedCacheParameters.Add("xms_acb", acbAuthN); - AdditionalResponseParameters = (IReadOnlyDictionary)msalAccessTokenCacheItem.PersistedCacheParameters; - } - else - { - if (additionalResponseParameters == null) - { - additionalResponseParameters = new Dictionary { { "xms_acb", acbAuthN } }; - } - else - { - Dictionary tempParams = additionalResponseParameters.ToDictionary(kvp => kvp.Key, kvp => kvp.Value); - tempParams.Add("xms_acb", acbAuthN); - additionalResponseParameters = tempParams; - } - - AdditionalResponseParameters = additionalResponseParameters; - } - } - else - { - AdditionalResponseParameters = msalAccessTokenCacheItem?.PersistedCacheParameters?.Count > 0 ? - (IReadOnlyDictionary)msalAccessTokenCacheItem.PersistedCacheParameters : - additionalResponseParameters; - } + AdditionalResponseParameters = msalAccessTokenCacheItem?.PersistedCacheParameters?.Count > 0 ? + (IReadOnlyDictionary)msalAccessTokenCacheItem.PersistedCacheParameters : + additionalResponseParameters; if (msalAccessTokenCacheItem != null) { diff --git a/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs b/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs index c2358a136f..926ec5e4c8 100644 --- a/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs +++ b/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs @@ -37,8 +37,7 @@ internal MsalAccessTokenCacheItem( string keyId = null, string oboCacheKey = null, IEnumerable persistedCacheParameters = null, - SortedList cacheKeyComponents = null, - string acbAuthN = null) + SortedList cacheKeyComponents = null) : this( scopes: ScopeHelper.OrderScopesAlphabetically(response.Scope), // order scopes to avoid cache duplication. This is not in the hot path. cachedAt: DateTimeOffset.UtcNow, @@ -55,7 +54,6 @@ internal MsalAccessTokenCacheItem( RawClientInfo = response.ClientInfo; HomeAccountId = homeAccountId; OboCacheKey = oboCacheKey; - AcbAuthN = acbAuthN; InitializeAdditionalCacheKeyComponents(cacheKeyComponents); #if !MOBILE @@ -90,6 +88,25 @@ private IDictionary AcquireCacheParametersFromResponse( #endif return cacheParameters; } + + internal void AddPersistedCacheParameters(Dictionary additionalPersistedCacheParameters) + { + if (additionalPersistedCacheParameters != null) + { + if (PersistedCacheParameters == null) + { + PersistedCacheParameters = new Dictionary(additionalPersistedCacheParameters); + } + else + { + foreach (var kvp in additionalPersistedCacheParameters) + { + PersistedCacheParameters[kvp.Key] = kvp.Value; + } + } + } + } + #endif internal /* for test */ MsalAccessTokenCacheItem( string preferredCacheEnv, @@ -326,7 +343,6 @@ internal static MsalAccessTokenCacheItem FromJObject(JObject j) string scopes = JsonHelper.ExtractExistingOrEmptyString(j, StorageJsonKeys.Target); var additionalCacheKeyComponents = JsonHelper.ExtractInnerJsonAsDictionary(j, StorageJsonKeys.CacheExtensions); var persistedCacheParameters = JsonHelper.ExtractInnerJsonAsDictionary(j, StorageJsonKeys.PersistedCacheParameters); - string acbAuthN = JsonHelper.ExtractExistingOrDefault(j, StorageJsonKeys.AcbAuthN); var item = new MsalAccessTokenCacheItem( scopes: scopes, @@ -346,7 +362,6 @@ internal static MsalAccessTokenCacheItem FromJObject(JObject j) item.PersistedCacheParameters = persistedCacheParameters; item.OboCacheKey = oboCacheKey; - item.AcbAuthN = acbAuthN; item.PopulateFieldsFromJObject(j); item.InitCacheKey(); @@ -371,7 +386,6 @@ internal override JObject ToJObject() json, StorageJsonKeys.RefreshOn, RefreshOn.HasValue ? DateTimeHelpers.DateTimeToUnixTimestamp(RefreshOn.Value) : null); - SetItemIfValueNotNull(json, StorageJsonKeys.AcbAuthN, AcbAuthN); // previous versions of MSAL used "ext_expires_on" instead of the correct "extended_expires_on". // this is here for back compatibility diff --git a/src/client/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs b/src/client/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs index 32bb244bac..0faf82843c 100644 --- a/src/client/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs +++ b/src/client/Microsoft.Identity.Client/Cache/StorageJsonKeys.cs @@ -44,6 +44,5 @@ internal static class StorageJsonKeys public const string CacheExtensions = "ext"; public const string PersistedCacheParameters = "persisted_cache_parameters"; - public const string AcbAuthN = "xms_acb"; } } diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs b/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs index 5b504fb3ca..482bde58c8 100644 --- a/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs +++ b/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs @@ -14,7 +14,6 @@ namespace Microsoft.Identity.Client.Internal { - [JsonObject] [Preserve(AllMembers = true)] internal class ClientInfo @@ -25,10 +24,7 @@ internal class ClientInfo [JsonProperty(ClientInfoClaim.UniqueTenantIdentifier)] public string UniqueTenantIdentifier { get; set; } - [JsonProperty(ClientInfoClaim.AcbAuthN)] - private IEnumerable AcbAuthNRaw { get; set; } - - public string AcbAuthN => string.Join(" ", AcbAuthNRaw); + public Dictionary AdditionalResponseParameters { get; private set; } public static ClientInfo CreateFromJson(string clientInfo) { @@ -41,7 +37,34 @@ public static ClientInfo CreateFromJson(string clientInfo) try { - return JsonHelper.DeserializeFromJson(Base64UrlHelpers.DecodeBytes(clientInfo)); + var decodedBytes = Base64UrlHelpers.DecodeBytes(clientInfo); + + // Deserialize into a dictionary to get all properties + var allProperties = JsonHelper.DeserializeFromJson>(decodedBytes); + + var clientInfoObj = new ClientInfo(); + var additionalParams = new Dictionary(); + + // Extract known claims and store the rest in AdditionalResponseParameters + foreach (var kvp in allProperties) + { + if (kvp.Key == ClientInfoClaim.UniqueIdentifier) + { + clientInfoObj.UniqueObjectIdentifier = kvp.Value?.ToString(); + + } + else if (kvp.Key == ClientInfoClaim.UniqueTenantIdentifier) + { + clientInfoObj.UniqueTenantIdentifier = kvp.Value?.ToString(); + } + else + { + additionalParams[kvp.Key] = kvp.Value?.ToString() ?? string.Empty; + } + } + + clientInfoObj.AdditionalResponseParameters = additionalParams; + return clientInfoObj; } catch (Exception exc) { diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientInfoClaim.cs b/src/client/Microsoft.Identity.Client/Internal/ClientInfoClaim.cs index 4eebb51864..6168140587 100644 --- a/src/client/Microsoft.Identity.Client/Internal/ClientInfoClaim.cs +++ b/src/client/Microsoft.Identity.Client/Internal/ClientInfoClaim.cs @@ -7,6 +7,5 @@ internal static class ClientInfoClaim { public const string UniqueIdentifier = "uid"; public const string UniqueTenantIdentifier = "utid"; - public const string AcbAuthN = "xms_acb"; } } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs index 7c5971bbcb..e7c08f0fc8 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs @@ -309,8 +309,7 @@ private AuthenticationResult CreateAuthenticationResultFromCache(MsalAccessToken AuthenticationRequestParameters.RequestContext.ApiEvent, account: null, spaAuthCode: null, - additionalResponseParameters: null, - acbAuthN: cachedAccessTokenItem.AcbAuthN); + additionalResponseParameters: null); return authResult; } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 53ddcc16b1..dd96a5b759 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -330,7 +330,6 @@ protected async Task CacheTokenResponseAndCreateAuthentica ValidateAccountIdentifiers(clientInfoFromServer); } - msalTokenResponse.AcbAuthN = clientInfoFromServer.AcbAuthN; AuthenticationRequestParameters.RequestContext.Logger.Info("Saving token response to cache.."); var tuple = await CacheManager.SaveTokenResponseAsync(msalTokenResponse).ConfigureAwait(false); @@ -338,7 +337,7 @@ protected async Task CacheTokenResponseAndCreateAuthentica var idtItem = tuple.Item2; Account account = tuple.Item3; - //TODO Get client info from response and use it to + atItem.AddPersistedCacheParameters(clientInfoFromServer.AdditionalResponseParameters); return new AuthenticationResult( atItem, @@ -349,8 +348,7 @@ protected async Task CacheTokenResponseAndCreateAuthentica AuthenticationRequestParameters.RequestContext.ApiEvent, account, msalTokenResponse.SpaAuthCode, - msalTokenResponse.CreateExtensionDataStringMap(), - acbAuthN: clientInfoFromServer.AcbAuthN); + msalTokenResponse.CreateExtensionDataStringMap()); } protected virtual void ValidateAccountIdentifiers(ClientInfo fromServer) diff --git a/src/client/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs b/src/client/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs index 8e16e5f257..2898c073aa 100644 --- a/src/client/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs +++ b/src/client/Microsoft.Identity.Client/OAuth2/MsalTokenResponse.cs @@ -183,8 +183,6 @@ public IReadOnlyDictionary CreateExtensionDataStringMap() public HttpResponse HttpResponse { get; set; } - public string AcbAuthN { get; set; } - internal static MsalTokenResponse CreateFromiOSBrokerResponse(Dictionary responseDictionary) { if (responseDictionary.TryGetValue(BrokerResponseConst.BrokerErrorCode, out string errorCode)) diff --git a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs index 76a5aef423..58fd9183a9 100644 --- a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs +++ b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs @@ -82,8 +82,7 @@ async Task> IToke requestParams.AuthenticationScheme.KeyId, CacheKeyFactory.GetOboKey(requestParams.LongRunningOboCacheKey, requestParams.UserAssertion), requestParams.PersistedCacheParameters, - requestParams.CacheKeyComponents, - response.AcbAuthN); + requestParams.CacheKeyComponents); //TODO need client info here } From 4d2dadcd41d8474ceb0b7751e8cf46935e0b02cf Mon Sep 17 00:00:00 2001 From: trwalke Date: Tue, 21 Oct 2025 11:07:05 -0700 Subject: [PATCH 07/14] Update test --- .../PublicApiTests/ConfidentialClientApplicationTests.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs index df9ca7b07c..03d3cb715e 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs @@ -2343,7 +2343,7 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsXmsAcbTestAsyn Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("xms_acb")); - Assert.AreEqual("value1 value2", result.AdditionalResponseParameters["xms_acb"]); + Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["xms_acb"]); // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); @@ -2362,7 +2362,7 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsXmsAcbTestAsyn Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("xms_acb")); - Assert.AreEqual("value1 value2", result.AdditionalResponseParameters["xms_acb"]); + Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["xms_acb"]); // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); From 52064fcc8c1b045efa51025e2d415b56b3d8b047 Mon Sep 17 00:00:00 2001 From: trwalke Date: Thu, 23 Oct 2025 14:56:43 -0700 Subject: [PATCH 08/14] Refactoring --- .../Cache/Items/MsalAccessTokenCacheItem.cs | 10 ++++------ .../Internal/Requests/RequestBase.cs | 2 +- .../TokenCache.ITokenCacheInternal.cs | 1 - .../Core/Mocks/MockHelpers.cs | 13 ++++--------- .../Core/Mocks/MockHttpManagerExtensions.cs | 2 +- 5 files changed, 10 insertions(+), 18 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs b/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs index 926ec5e4c8..907494fb4c 100644 --- a/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs +++ b/src/client/Microsoft.Identity.Client/Cache/Items/MsalAccessTokenCacheItem.cs @@ -89,17 +89,17 @@ private IDictionary AcquireCacheParametersFromResponse( return cacheParameters; } - internal void AddPersistedCacheParameters(Dictionary additionalPersistedCacheParameters) + internal void AddAdditionalCacheParameters(Dictionary additionalCacheParameters) { - if (additionalPersistedCacheParameters != null) + if (additionalCacheParameters != null) { if (PersistedCacheParameters == null) { - PersistedCacheParameters = new Dictionary(additionalPersistedCacheParameters); + PersistedCacheParameters = new Dictionary(additionalCacheParameters); } else { - foreach (var kvp in additionalPersistedCacheParameters) + foreach (var kvp in additionalCacheParameters) { PersistedCacheParameters[kvp.Key] = kvp.Value; } @@ -307,8 +307,6 @@ internal string TenantId /// internal IDictionary PersistedCacheParameters { get; private set; } - internal string AcbAuthN { get; private set; } - private Lazy iOSCacheKeyLazy; public IiOSKey iOSCacheKey => iOSCacheKeyLazy.Value; diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index dd96a5b759..338057021b 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -337,7 +337,7 @@ protected async Task CacheTokenResponseAndCreateAuthentica var idtItem = tuple.Item2; Account account = tuple.Item3; - atItem.AddPersistedCacheParameters(clientInfoFromServer.AdditionalResponseParameters); + atItem.AddAdditionalCacheParameters(clientInfoFromServer.AdditionalResponseParameters); return new AuthenticationResult( atItem, diff --git a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs index 58fd9183a9..f0fefb153f 100644 --- a/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs +++ b/src/client/Microsoft.Identity.Client/TokenCache.ITokenCacheInternal.cs @@ -83,7 +83,6 @@ async Task> IToke CacheKeyFactory.GetOboKey(requestParams.LongRunningOboCacheKey, requestParams.UserAssertion), requestParams.PersistedCacheParameters, requestParams.CacheKeyComponents); - //TODO need client info here } if (!string.IsNullOrEmpty(response.RefreshToken)) diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs index 7a73f4816f..f68ad86f0d 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs @@ -170,15 +170,10 @@ public static string GetMsiImdsErrorResponse() "\"correlation_id\":\"77145480-bc5a-4ebe-ae4d-e4a8b7d727cf\",\"error_uri\":\"https://westus2.login.microsoft.com/error?code=500011\"}"; } - public static string CreateClientInfo(string uid = TestConstants.Uid, string utid = TestConstants.Utid, bool addXms_acb = false) + public static string CreateClientInfo(string uid = TestConstants.Uid, string utid = TestConstants.Utid, bool CreateClientInfoForS2S = false) { - if (addXms_acb) + if (CreateClientInfoForS2S) { - if (!string.IsNullOrEmpty(uid)) - { - return Base64UrlHelpers.Encode("{\"uid\":\"" + uid + "\",\"utid\":\"" + utid + "\",\"xms_acb\":[\"value1\",\"value2\"]}"); - } - return Base64UrlHelpers.Encode("{\"xms_acb\":[\"value1\",\"value2\"]}"); } @@ -368,11 +363,11 @@ public static HttpResponseMessage CreateSuccessfulClientCredentialTokenResponseW string token = "header.payload.signature", string expiry = "3599", string tokenType = "Bearer", - bool addXms_acb = false + bool CreateClientInfoForS2S = false ) { return CreateSuccessResponseMessage( - "{\"token_type\":\"" + tokenType + "\",\"expires_in\":\"" + expiry + "\",\"access_token\":\"" + token + "\",\"additional_param1\":\"value1\",\"additional_param2\":\"value2\",\"additional_param3\":\"value3\",\"client_info\":\"" + CreateClientInfo(null, null, addXms_acb) + "\"}"); + "{\"token_type\":\"" + tokenType + "\",\"expires_in\":\"" + expiry + "\",\"access_token\":\"" + token + "\",\"additional_param1\":\"value1\",\"additional_param2\":\"value2\",\"additional_param3\":\"value3\",\"client_info\":\"" + CreateClientInfo(null, null, CreateClientInfoForS2S) + "\"}"); } public static HttpResponseMessage CreateSuccessfulClientCredentialTokenResponseWithAdditionalParamsMessage( diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs index 867e111230..561fdfc280 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHttpManagerExtensions.cs @@ -195,7 +195,7 @@ public static MockHttpMessageHandler AddMockHandlerSuccessfulClientCredentialTok var handler = new MockHttpMessageHandler() { ExpectedMethod = HttpMethod.Post, - ResponseMessage = addClientInfo? MockHelpers.CreateSuccessfulClientCredentialTokenResponseWithClientInfoMessage(token, expiresIn, tokenType, addClientInfo) + ResponseMessage = addClientInfo? MockHelpers.CreateSuccessfulClientCredentialTokenResponseWithClientInfoMessage(token, expiresIn, tokenType, true) : MockHelpers.CreateSuccessfulClientCredentialTokenResponseMessage(token, expiresIn, tokenType), UnexpectedRequestHeaders = unexpectedHttpHeaders, ExpectedPostData = expectedPostData From 14012128a5a8ea0ad1fa032b61e83cdf1503c6c6 Mon Sep 17 00:00:00 2001 From: trwalke Date: Thu, 30 Oct 2025 01:40:04 -0700 Subject: [PATCH 09/14] Updating test --- .../Microsoft.Identity.Client/AuthenticationResult.cs | 1 - .../Core/Mocks/MockHelpers.cs | 2 +- .../ConfidentialClientApplicationTests.cs | 10 +++++----- 3 files changed, 6 insertions(+), 7 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs index f9cdf6b5df..ccf501aba8 100644 --- a/src/client/Microsoft.Identity.Client/AuthenticationResult.cs +++ b/src/client/Microsoft.Identity.Client/AuthenticationResult.cs @@ -13,7 +13,6 @@ using Microsoft.Identity.Client.TelemetryCore.Internal.Events; using Microsoft.Identity.Client.Utils; using System.Security.Cryptography.X509Certificates; -using System.Linq; namespace Microsoft.Identity.Client { diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs index f68ad86f0d..bfc7541b89 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs @@ -174,7 +174,7 @@ public static string CreateClientInfo(string uid = TestConstants.Uid, string uti { if (CreateClientInfoForS2S) { - return Base64UrlHelpers.Encode("{\"xms_acb\":[\"value1\",\"value2\"]}"); + return Base64UrlHelpers.Encode("{\"authZ\":[\"value1\",\"value2\"]}"); } return Base64UrlHelpers.Encode("{\"uid\":\"" + uid + "\",\"utid\":\"" + utid + "\"}"); diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs index 03d3cb715e..4090b330b4 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs @@ -2321,7 +2321,7 @@ public async Task AcquireTokenForClient_WithClaims_And_MismatchedHash_UsesCache_ } [TestMethod] - public async Task ConfidentialClient_acquireTokenForClient_ReturnsXmsAcbTestAsync() + public async Task ConfidentialClient_acquireTokenForClient_ReturnsAuthZTestAsync() { using (var httpManager = new MockHttpManager()) { @@ -2342,8 +2342,8 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsXmsAcbTestAsyn Assert.IsNotNull(result); Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); - Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("xms_acb")); - Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["xms_acb"]); + Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("authZ")); + Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["authZ"]); // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); @@ -2361,8 +2361,8 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsXmsAcbTestAsyn Assert.IsNotNull(result); Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); - Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("xms_acb")); - Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["xms_acb"]); + Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("authZ")); + Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["authZ"]); // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); From af86384e4e26f0ae12657039af0351815079f4cd Mon Sep 17 00:00:00 2001 From: trwalke Date: Thu, 30 Oct 2025 02:08:12 -0700 Subject: [PATCH 10/14] Resolving null ref --- .../Microsoft.Identity.Client/Internal/Requests/RequestBase.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 338057021b..467b196b6a 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -337,7 +337,7 @@ protected async Task CacheTokenResponseAndCreateAuthentica var idtItem = tuple.Item2; Account account = tuple.Item3; - atItem.AddAdditionalCacheParameters(clientInfoFromServer.AdditionalResponseParameters); + atItem.AddAdditionalCacheParameters(clientInfoFromServer?.AdditionalResponseParameters); return new AuthenticationResult( atItem, From 1c4e2de6920d376114df01bea169501d7e026e03 Mon Sep 17 00:00:00 2001 From: trwalke Date: Fri, 31 Oct 2025 00:54:46 -0700 Subject: [PATCH 11/14] Updating test data --- .../Microsoft.Identity.Client/Internal/ClientInfo.cs | 2 +- .../Core/Mocks/MockHelpers.cs | 2 +- .../PublicApiTests/ConfidentialClientApplicationTests.cs | 8 ++++---- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs b/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs index 482bde58c8..595d0bf996 100644 --- a/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs +++ b/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs @@ -40,7 +40,7 @@ public static ClientInfo CreateFromJson(string clientInfo) var decodedBytes = Base64UrlHelpers.DecodeBytes(clientInfo); // Deserialize into a dictionary to get all properties - var allProperties = JsonHelper.DeserializeFromJson>(decodedBytes); + var allProperties = JsonHelper.DeserializeFromJson>(decodedBytes); var clientInfoObj = new ClientInfo(); var additionalParams = new Dictionary(); diff --git a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs index caeee58d5d..0cd3aab2a6 100644 --- a/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs +++ b/tests/Microsoft.Identity.Test.Common/Core/Mocks/MockHelpers.cs @@ -188,7 +188,7 @@ public static string CreateClientInfo(string uid = TestConstants.Uid, string uti { if (CreateClientInfoForS2S) { - return Base64UrlHelpers.Encode("{\"authZ\":[\"value1\",\"value2\"]}"); + return Base64UrlHelpers.Encode("{\"authz\":[\"value1\",\"value2\"]}"); } return Base64UrlHelpers.Encode("{\"uid\":\"" + uid + "\",\"utid\":\"" + utid + "\"}"); diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs index 74995abfef..5126ff0df8 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs @@ -2310,8 +2310,8 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsAuthZTestAsync Assert.IsNotNull(result); Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); - Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("authZ")); - Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["authZ"]); + Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("authz")); + Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["authz"]); // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); @@ -2329,8 +2329,8 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsAuthZTestAsync Assert.IsNotNull(result); Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); - Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("authZ")); - Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["authZ"]); + Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("authz")); + Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["authz"]); // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); From 60b5c4494df41f43fc557a53dfd10ee6d08c481d Mon Sep 17 00:00:00 2001 From: trwalke Date: Fri, 31 Oct 2025 02:23:49 -0700 Subject: [PATCH 12/14] Fixing test errors --- .../Microsoft.Identity.Client/Internal/ClientInfo.cs | 2 +- .../Platforms/net/JsonStringConverter.cs | 12 ++++++++++++ .../Platforms/net/MsalJsonSerializerContext.cs | 1 + .../ConfidentialClientApplicationTests.cs | 10 ++++++++-- 4 files changed, 22 insertions(+), 3 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs b/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs index 595d0bf996..482bde58c8 100644 --- a/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs +++ b/src/client/Microsoft.Identity.Client/Internal/ClientInfo.cs @@ -40,7 +40,7 @@ public static ClientInfo CreateFromJson(string clientInfo) var decodedBytes = Base64UrlHelpers.DecodeBytes(clientInfo); // Deserialize into a dictionary to get all properties - var allProperties = JsonHelper.DeserializeFromJson>(decodedBytes); + var allProperties = JsonHelper.DeserializeFromJson>(decodedBytes); var clientInfoObj = new ClientInfo(); var additionalParams = new Dictionary(); diff --git a/src/client/Microsoft.Identity.Client/Platforms/net/JsonStringConverter.cs b/src/client/Microsoft.Identity.Client/Platforms/net/JsonStringConverter.cs index 337bc49b2c..b4449cb36a 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/net/JsonStringConverter.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/net/JsonStringConverter.cs @@ -38,5 +38,17 @@ public override void Write(Utf8JsonWriter writer, string value, JsonSerializerOp { writer.WriteStringValue(value); } + + //Provides Support for reading dictionary key strings + public override string ReadAsPropertyName(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + return reader.GetString(); + } + + //Provides Support for writing dictionary key strings + public override void WriteAsPropertyName(Utf8JsonWriter writer, string value, JsonSerializerOptions options) + { + writer.WritePropertyName(value); + } } } diff --git a/src/client/Microsoft.Identity.Client/Platforms/net/MsalJsonSerializerContext.cs b/src/client/Microsoft.Identity.Client/Platforms/net/MsalJsonSerializerContext.cs index ea9a757a33..4fc00ab332 100644 --- a/src/client/Microsoft.Identity.Client/Platforms/net/MsalJsonSerializerContext.cs +++ b/src/client/Microsoft.Identity.Client/Platforms/net/MsalJsonSerializerContext.cs @@ -42,6 +42,7 @@ namespace Microsoft.Identity.Client.Platforms.net [JsonSerializable(typeof(CuidInfo))] [JsonSerializable(typeof(CertificateRequestBody))] [JsonSerializable(typeof(CertificateRequestResponse))] + [JsonSerializable(typeof(Dictionary))] [JsonSourceGenerationOptions] internal partial class MsalJsonSerializerContext : JsonSerializerContext { diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs index 5126ff0df8..b2ab2b6bfb 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/ConfidentialClientApplicationTests.cs @@ -2311,8 +2311,11 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsAuthZTestAsync Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("authz")); +#if SUPPORTS_SYSTEM_TEXT_JSON + Assert.AreEqual("[\"value1\",\"value2\"]", result.AdditionalResponseParameters["authz"]); +#else Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["authz"]); - +#endif // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count); @@ -2330,8 +2333,11 @@ public async Task ConfidentialClient_acquireTokenForClient_ReturnsAuthZTestAsync Assert.AreEqual("header.payload.signature", result.AccessToken); Assert.AreEqual(TestConstants.s_scope.AsSingleString(), result.Scopes.AsSingleString()); Assert.IsTrue(result.AdditionalResponseParameters.ContainsKey("authz")); +#if SUPPORTS_SYSTEM_TEXT_JSON + Assert.AreEqual("[\"value1\",\"value2\"]", result.AdditionalResponseParameters["authz"]); +#else Assert.AreEqual("[\r\n \"value1\",\r\n \"value2\"\r\n]", result.AdditionalResponseParameters["authz"]); - +#endif // make sure user token cache is empty Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Count); Assert.AreEqual(0, app.UserTokenCacheInternal.Accessor.GetAllRefreshTokens().Count); From 7f237d5ae6db97fbeaad7a0c79f731a1b6240389 Mon Sep 17 00:00:00 2001 From: trwalke Date: Fri, 31 Oct 2025 02:26:31 -0700 Subject: [PATCH 13/14] Fixing build --- .../Internal/Requests/RequestBase.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 467b196b6a..85b32b22b9 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -336,9 +336,9 @@ protected async Task CacheTokenResponseAndCreateAuthentica var atItem = tuple.Item1; var idtItem = tuple.Item2; Account account = tuple.Item3; - +#if !MOBILE atItem.AddAdditionalCacheParameters(clientInfoFromServer?.AdditionalResponseParameters); - +#endif return new AuthenticationResult( atItem, idtItem, From 74b52cd0dd3b4f370f2d5e81e6ca8e14729b9761 Mon Sep 17 00:00:00 2001 From: trwalke Date: Fri, 31 Oct 2025 10:04:12 -0700 Subject: [PATCH 14/14] Fixed test errors --- .../Internal/Requests/RequestBase.cs | 2 +- .../UtilTests/JsonHelperTests.cs | 5 ++++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs index 85b32b22b9..054bf33624 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/RequestBase.cs @@ -337,7 +337,7 @@ protected async Task CacheTokenResponseAndCreateAuthentica var idtItem = tuple.Item2; Account account = tuple.Item3; #if !MOBILE - atItem.AddAdditionalCacheParameters(clientInfoFromServer?.AdditionalResponseParameters); + atItem?.AddAdditionalCacheParameters(clientInfoFromServer?.AdditionalResponseParameters); #endif return new AuthenticationResult( atItem, diff --git a/tests/Microsoft.Identity.Test.Unit/UtilTests/JsonHelperTests.cs b/tests/Microsoft.Identity.Test.Unit/UtilTests/JsonHelperTests.cs index 0365a8fa11..81cd0c4400 100644 --- a/tests/Microsoft.Identity.Test.Unit/UtilTests/JsonHelperTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/UtilTests/JsonHelperTests.cs @@ -62,11 +62,14 @@ public void Serialize_ClientInfo() ClientInfo clientInfo = new ClientInfo() { UniqueObjectIdentifier = "some_uid", UniqueTenantIdentifier = "some_tid" }; string actualJson = JsonHelper.SerializeToJson(clientInfo); +#if SUPPORTS_SYSTEM_TEXT_JSON string expectedJson = @"{ ""uid"": ""some_uid"", ""utid"": ""some_tid"" }"; - +#else + string expectedJson = @"{""uid"":""some_uid"",""utid"":""some_tid"",""AdditionalResponseParameters"":null}"; +#endif JsonTestUtils.AssertJsonDeepEquals(expectedJson, actualJson); }