From d15b71d2de55da97fd10b000625da8ddf24b30f7 Mon Sep 17 00:00:00 2001 From: trwalke Date: Mon, 3 Nov 2025 03:55:10 -0800 Subject: [PATCH 1/5] Initial prototype --- .../Bearer/BearerAuthenticationOperation.cs | 6 ++++++ .../AuthScheme/IAuthenticationOperation.cs | 7 +++++++ .../AuthScheme/MsalCacheValidationData.cs | 18 ++++++++++++++++++ .../PoP/MtlsPopAuthenticationOperation.cs | 6 ++++++ .../PoP/PopAuthenticationOperation.cs | 6 ++++++ .../PoP/PopBrokerAuthenticationOperation.cs | 6 ++++++ .../SSHCertAuthenticationOperation.cs | 6 ++++++ .../Extensibility/ExternalBoundTokenScheme.cs | 6 ++++++ .../Requests/ClientCredentialRequest.cs | 14 ++++++++++++++ .../Microsoft.Identity.Client.csproj | 5 +++++ .../PublicApi/net462/PublicAPI.Unshipped.txt | 4 ++++ .../PublicApi/net472/PublicAPI.Unshipped.txt | 4 ++++ .../net8.0-android/PublicAPI.Unshipped.txt | 4 ++++ .../net8.0-ios/PublicAPI.Unshipped.txt | 4 ++++ .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 4 ++++ .../netstandard2.0/PublicAPI.Unshipped.txt | 4 ++++ 16 files changed, 104 insertions(+) create mode 100644 src/client/Microsoft.Identity.Client/AuthScheme/MsalCacheValidationData.cs diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs index 4e1dc687e9..5e5899d703 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs @@ -30,5 +30,11 @@ public IReadOnlyDictionary GetTokenRequestParams() // ESTS issues Bearer tokens by default, no need for any extra params return CollectionHelpers.GetEmptyDictionary(); } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs index 1ed04ea6e2..1718872d1f 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs @@ -43,6 +43,13 @@ public interface IAuthenticationOperation /// Name and values of params IReadOnlyDictionary GetTokenRequestParams(); + /// + /// + /// + /// + /// + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem); + /// /// Key ID of the public / private key pair used by the encryption algorithm, if any. /// Tokens obtained by authentication schemes that use this are bound to the KeyId, i.e. diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/MsalCacheValidationData.cs b/src/client/Microsoft.Identity.Client/AuthScheme/MsalCacheValidationData.cs new file mode 100644 index 0000000000..a0e95d28c8 --- /dev/null +++ b/src/client/Microsoft.Identity.Client/AuthScheme/MsalCacheValidationData.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Identity.Client.AuthScheme +{ + /// + /// Data used to validate cache items for different authentication schemes. + /// + public class MsalCacheValidationData + { + /// + /// Gets the persisted parameters addded to the cache items. + /// + public IDictionary PersistedCacheParameters { get; internal set; } + } +} diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs index 980ac388f7..ee79d30564 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs @@ -40,5 +40,11 @@ public void FormatResult(AuthenticationResult authenticationResult) { authenticationResult.BindingCertificate = _mtlsCert; } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs index a2723fe9b9..b52950a643 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs @@ -162,5 +162,11 @@ private string CreateJWS(string payload, string header) return sb.ToString(); } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs index c81f411f41..55dfb397be 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs @@ -34,5 +34,11 @@ public IReadOnlyDictionary GetTokenRequestParams() { return CollectionHelpers.GetEmptyDictionary(); } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs index cc64c2db61..198133298f 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs @@ -53,5 +53,11 @@ public IReadOnlyDictionary GetTokenRequestParams() { OAuth2Parameter.RequestConfirmation , _jwk } }; } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs b/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs index d622bbbae7..9c4fe976ff 100644 --- a/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs +++ b/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs @@ -37,5 +37,11 @@ public IReadOnlyDictionary GetTokenRequestParams() { return CollectionHelpers.GetEmptyDictionary(); } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs index e7c08f0fc8..d15aa6bb51 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client.ApiConfig.Parameters; +using Microsoft.Identity.Client.AuthScheme; using Microsoft.Identity.Client.Cache.Items; using Microsoft.Identity.Client.Core; using Microsoft.Identity.Client.Extensibility; @@ -76,6 +77,19 @@ protected override async Task ExecuteAsync(CancellationTok MsalAccessTokenCacheItem cachedAccessTokenItem = await GetCachedAccessTokenAsync().ConfigureAwait(false); + // Validate the cached token using the authentication operation + if (AuthenticationRequestParameters.AuthenticationScheme != null && + cachedAccessTokenItem != null) + { + var cacheValidationData = new MsalCacheValidationData(); + cacheValidationData.PersistedCacheParameters = cachedAccessTokenItem.PersistedCacheParameters; + if (!AuthenticationRequestParameters.AuthenticationScheme.ValidateCachedToken(cacheValidationData)) + { + logger.Info("[ClientCredentialRequest] Cached token failed authentication operation validation."); + cachedAccessTokenItem = null; + } + } + // No access token or cached access token needs to be refreshed if (cachedAccessTokenItem != null) { diff --git a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj index 3279f0338a..d80d8cf17a 100644 --- a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj +++ b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj @@ -80,6 +80,7 @@ + @@ -161,4 +162,8 @@ + + + + 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 4971f00461..134e4ba292 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder 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 4971f00461..134e4ba292 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder 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 4971f00461..134e4ba292 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 @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder 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 4971f00461..134e4ba292 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 @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder 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 4971f00461..134e4ba292 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 @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder 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 4971f00461..134e4ba292 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 @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder From de7adf51491a82d039cb6a72671b2c9a3839f8a3 Mon Sep 17 00:00:00 2001 From: trwalke Date: Mon, 3 Nov 2025 03:55:10 -0800 Subject: [PATCH 2/5] Initial prototype --- .../Bearer/BearerAuthenticationOperation.cs | 6 ++++++ .../AuthScheme/IAuthenticationOperation.cs | 7 +++++++ .../AuthScheme/MsalCacheValidationData.cs | 18 ++++++++++++++++++ .../PoP/MtlsPopAuthenticationOperation.cs | 6 ++++++ .../PoP/PopAuthenticationOperation.cs | 6 ++++++ .../PoP/PopBrokerAuthenticationOperation.cs | 6 ++++++ .../SSHCertAuthenticationOperation.cs | 6 ++++++ .../Extensibility/ExternalBoundTokenScheme.cs | 6 ++++++ .../Requests/ClientCredentialRequest.cs | 14 ++++++++++++++ .../Microsoft.Identity.Client.csproj | 5 +++++ .../PublicApi/net462/PublicAPI.Unshipped.txt | 4 ++++ .../PublicApi/net472/PublicAPI.Unshipped.txt | 4 ++++ .../net8.0-android/PublicAPI.Unshipped.txt | 4 ++++ .../net8.0-ios/PublicAPI.Unshipped.txt | 4 ++++ .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 4 ++++ .../netstandard2.0/PublicAPI.Unshipped.txt | 4 ++++ 16 files changed, 104 insertions(+) create mode 100644 src/client/Microsoft.Identity.Client/AuthScheme/MsalCacheValidationData.cs diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs index b512fc78a7..04ee72a64c 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs @@ -38,5 +38,11 @@ public IReadOnlyDictionary GetTokenRequestParams() // ESTS issues Bearer tokens by default, no need for any extra params return CollectionHelpers.GetEmptyDictionary(); } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs index 739ca6b1aa..527d8dcb41 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs @@ -42,6 +42,13 @@ public interface IAuthenticationOperation /// Name and values of params IReadOnlyDictionary GetTokenRequestParams(); + /// + /// + /// + /// + /// + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem); + /// /// Key ID of the public / private key pair used by the encryption algorithm, if any. /// Tokens obtained by authentication schemes that use this are bound to the KeyId, i.e. diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/MsalCacheValidationData.cs b/src/client/Microsoft.Identity.Client/AuthScheme/MsalCacheValidationData.cs new file mode 100644 index 0000000000..a0e95d28c8 --- /dev/null +++ b/src/client/Microsoft.Identity.Client/AuthScheme/MsalCacheValidationData.cs @@ -0,0 +1,18 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System.Collections.Generic; + +namespace Microsoft.Identity.Client.AuthScheme +{ + /// + /// Data used to validate cache items for different authentication schemes. + /// + public class MsalCacheValidationData + { + /// + /// Gets the persisted parameters addded to the cache items. + /// + public IDictionary PersistedCacheParameters { get; internal set; } + } +} diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs index f74f1b0859..a82b2c5ab4 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs @@ -48,5 +48,11 @@ public void FormatResult(AuthenticationResult authenticationResult) { authenticationResult.BindingCertificate = _mtlsCert; } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs index 934d42d688..6157d35f0b 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs @@ -172,5 +172,11 @@ private string CreateJWS(string payload, string header) return sb.ToString(); } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs index e9e6e7a388..7edec724ac 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs @@ -40,5 +40,11 @@ public IReadOnlyDictionary GetTokenRequestParams() { return CollectionHelpers.GetEmptyDictionary(); } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs index 5a5a7cd04e..bfad8debf0 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs @@ -60,5 +60,11 @@ public IReadOnlyDictionary GetTokenRequestParams() { OAuth2Parameter.RequestConfirmation , _jwk } }; } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs b/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs index 84e1830893..b0f8fd8f39 100644 --- a/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs +++ b/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs @@ -44,5 +44,11 @@ public IReadOnlyDictionary GetTokenRequestParams() { return CollectionHelpers.GetEmptyDictionary(); } + + bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + { + // no-op + return true; + } } } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs index 5e120eec6a..c203528fc9 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs @@ -10,6 +10,7 @@ using System.Threading; using System.Threading.Tasks; using Microsoft.Identity.Client.ApiConfig.Parameters; +using Microsoft.Identity.Client.AuthScheme; using Microsoft.Identity.Client.Cache.Items; using Microsoft.Identity.Client.Core; using Microsoft.Identity.Client.Extensibility; @@ -76,6 +77,19 @@ protected override async Task ExecuteAsync(CancellationTok MsalAccessTokenCacheItem cachedAccessTokenItem = await GetCachedAccessTokenAsync().ConfigureAwait(false); + // Validate the cached token using the authentication operation + if (AuthenticationRequestParameters.AuthenticationScheme != null && + cachedAccessTokenItem != null) + { + var cacheValidationData = new MsalCacheValidationData(); + cacheValidationData.PersistedCacheParameters = cachedAccessTokenItem.PersistedCacheParameters; + if (!AuthenticationRequestParameters.AuthenticationScheme.ValidateCachedToken(cacheValidationData)) + { + logger.Info("[ClientCredentialRequest] Cached token failed authentication operation validation."); + cachedAccessTokenItem = null; + } + } + // No access token or cached access token needs to be refreshed if (cachedAccessTokenItem != null) { diff --git a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj index 3279f0338a..d80d8cf17a 100644 --- a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj +++ b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj @@ -80,6 +80,7 @@ + @@ -161,4 +162,8 @@ + + + + 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 c000207bbf..e39e9a0e00 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task 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 c000207bbf..e39e9a0e00 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task 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 c000207bbf..e39e9a0e00 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 @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task 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 c000207bbf..e39e9a0e00 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 @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task 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 c000207bbf..e39e9a0e00 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 @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task 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 c000207bbf..e39e9a0e00 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 @@ -2,6 +2,10 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string +Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void +Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary Microsoft.Identity.Client.IMsalMtlsHttpClientFactory Microsoft.Identity.Client.IMsalMtlsHttpClientFactory.GetHttpClient(System.Security.Cryptography.X509Certificates.X509Certificate2 x509Certificate2) -> System.Net.Http.HttpClient Microsoft.Identity.Client.ManagedIdentityApplication.GetManagedIdentitySourceAsync() -> System.Threading.Tasks.Task From ef2a317bafb881083850d88e11f6380b13b5f910 Mon Sep 17 00:00:00 2001 From: trwalke Date: Tue, 11 Nov 2025 16:20:15 -0800 Subject: [PATCH 3/5] Refactoring --- .../Bearer/BearerAuthenticationOperation.cs | 4 +- .../AuthScheme/IAuthenticationOperation.cs | 7 - .../AuthScheme/IAuthenticationOperation2.cs | 7 + .../PoP/MtlsPopAuthenticationOperation.cs | 4 +- .../PoP/PopAuthenticationOperation.cs | 4 +- .../PoP/PopBrokerAuthenticationOperation.cs | 4 +- .../SSHCertAuthenticationOperation.cs | 4 +- .../Extensibility/ExternalBoundTokenScheme.cs | 4 +- .../Requests/ClientCredentialRequest.cs | 6 +- .../PublicApi/net462/PublicAPI.Unshipped.txt | 4 +- .../PublicApi/net472/PublicAPI.Unshipped.txt | 4 +- .../net8.0-android/PublicAPI.Unshipped.txt | 4 +- .../net8.0-ios/PublicAPI.Unshipped.txt | 4 +- .../PublicApi/net8.0/PublicAPI.Unshipped.txt | 4 +- .../netstandard2.0/PublicAPI.Unshipped.txt | 4 +- .../AuthenticationOperationTests.cs | 164 ++++++++++++++++++ 16 files changed, 199 insertions(+), 33 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs index 04ee72a64c..e8679a6400 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/Bearer/BearerAuthenticationOperation.cs @@ -39,10 +39,10 @@ public IReadOnlyDictionary GetTokenRequestParams() return CollectionHelpers.GetEmptyDictionary(); } - bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + public Task ValidateCachedTokenAsync(MsalCacheValidationData cachedTokenData) { // no-op - return true; + return Task.FromResult(true); } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs index 527d8dcb41..739ca6b1aa 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation.cs @@ -42,13 +42,6 @@ public interface IAuthenticationOperation /// Name and values of params IReadOnlyDictionary GetTokenRequestParams(); - /// - /// - /// - /// - /// - bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem); - /// /// Key ID of the public / private key pair used by the encryption algorithm, if any. /// Tokens obtained by authentication schemes that use this are bound to the KeyId, i.e. diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs index 1bb1eaf932..104857b87a 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs @@ -17,5 +17,12 @@ public interface IAuthenticationOperation2 : IAuthenticationOperation /// Will be invoked instead of IAuthenticationOperation.FormatResult /// Task FormatResultAsync(AuthenticationResult authenticationResult, CancellationToken cancellationToken = default); + + /// + /// Determines whether the cached token is still valid. + /// + /// Data used to determine if token is valid + /// + Task ValidateCachedTokenAsync(MsalCacheValidationData cachedTokenData); } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs index a82b2c5ab4..4bd1913331 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/MtlsPopAuthenticationOperation.cs @@ -49,10 +49,10 @@ public void FormatResult(AuthenticationResult authenticationResult) authenticationResult.BindingCertificate = _mtlsCert; } - bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + public Task ValidateCachedTokenAsync(MsalCacheValidationData cachedTokenData) { // no-op - return true; + return Task.FromResult(true); } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs index 6157d35f0b..44b5856b89 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopAuthenticationOperation.cs @@ -173,10 +173,10 @@ private string CreateJWS(string payload, string header) return sb.ToString(); } - bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + public Task ValidateCachedTokenAsync(MsalCacheValidationData cachedTokenData) { // no-op - return true; + return Task.FromResult(true); } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs index 7edec724ac..fc27d10276 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/PoP/PopBrokerAuthenticationOperation.cs @@ -41,10 +41,10 @@ public IReadOnlyDictionary GetTokenRequestParams() return CollectionHelpers.GetEmptyDictionary(); } - bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + public Task ValidateCachedTokenAsync(MsalCacheValidationData cachedTokenData) { // no-op - return true; + return Task.FromResult(true); } } } diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs b/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs index bfad8debf0..caccfbe2e4 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/SSHCertificates/SSHCertAuthenticationOperation.cs @@ -61,10 +61,10 @@ public IReadOnlyDictionary GetTokenRequestParams() }; } - bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + public Task ValidateCachedTokenAsync(MsalCacheValidationData cachedTokenData) { // no-op - return true; + return Task.FromResult(true); } } } diff --git a/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs b/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs index b0f8fd8f39..7518d6c8c2 100644 --- a/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs +++ b/src/client/Microsoft.Identity.Client/Extensibility/ExternalBoundTokenScheme.cs @@ -45,10 +45,10 @@ public IReadOnlyDictionary GetTokenRequestParams() return CollectionHelpers.GetEmptyDictionary(); } - bool ValidateCachedToken(MsalCacheValidationData cachedTokenItem) + public Task ValidateCachedTokenAsync(MsalCacheValidationData cachedTokenData) { // no-op - return true; + return Task.FromResult(true); } } } diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs index c203528fc9..ad61253b96 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/ClientCredentialRequest.cs @@ -79,11 +79,13 @@ protected override async Task ExecuteAsync(CancellationTok // Validate the cached token using the authentication operation if (AuthenticationRequestParameters.AuthenticationScheme != null && - cachedAccessTokenItem != null) + cachedAccessTokenItem != null && + AuthenticationRequestParameters.AuthenticationScheme is IAuthenticationOperation2 authOp2) { var cacheValidationData = new MsalCacheValidationData(); cacheValidationData.PersistedCacheParameters = cachedAccessTokenItem.PersistedCacheParameters; - if (!AuthenticationRequestParameters.AuthenticationScheme.ValidateCachedToken(cacheValidationData)) + + if (!await authOp2.ValidateCachedTokenAsync(cacheValidationData).ConfigureAwait(false)) { logger.Info("[ClientCredentialRequest] Cached token failed authentication operation validation."); cachedAccessTokenItem = null; 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 e39e9a0e00..058ab31a1f 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net462/PublicAPI.Unshipped.txt @@ -2,7 +2,7 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string -Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.ValidateCachedTokenAsync(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenData) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary @@ -13,4 +13,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Mi Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 -Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task \ No newline at end of file +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task 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 e39e9a0e00..058ab31a1f 100644 --- a/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt +++ b/src/client/Microsoft.Identity.Client/PublicApi/net472/PublicAPI.Unshipped.txt @@ -2,7 +2,7 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string -Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.ValidateCachedTokenAsync(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenData) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary @@ -13,4 +13,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Mi Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 -Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task \ No newline at end of file +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task 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 e39e9a0e00..058ab31a1f 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 @@ -2,7 +2,7 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string -Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.ValidateCachedTokenAsync(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenData) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary @@ -13,4 +13,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Mi Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 -Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task \ No newline at end of file +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task 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 e39e9a0e00..058ab31a1f 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 @@ -2,7 +2,7 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string -Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.ValidateCachedTokenAsync(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenData) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary @@ -13,4 +13,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Mi Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 -Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task \ No newline at end of file +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task 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 e39e9a0e00..058ab31a1f 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 @@ -2,7 +2,7 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string -Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.ValidateCachedTokenAsync(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenData) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary @@ -13,4 +13,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Mi Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 -Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task \ No newline at end of file +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task 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 e39e9a0e00..058ab31a1f 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 @@ -2,7 +2,7 @@ const Microsoft.Identity.Client.MsalError.CannotSwitchBetweenImdsVersionsForPrev const Microsoft.Identity.Client.MsalError.InvalidCertificate = "invalid_certificate" -> string const Microsoft.Identity.Client.MsalError.MtlsNotSupportedForManagedIdentity = "mtls_not_supported_for_managed_identity" -> string const Microsoft.Identity.Client.MsalError.MtlsPopTokenNotSupportedinImdsV1 = "mtls_pop_token_not_supported_in_imds_v1" -> string -Microsoft.Identity.Client.AuthScheme.ValidateCachedToken(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenItem) -> bool +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.ValidateCachedTokenAsync(Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData cachedTokenData) -> System.Threading.Tasks.Task Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.MsalCacheValidationData() -> void Microsoft.Identity.Client.AuthScheme.MsalCacheValidationData.PersistedCacheParameters.get -> System.Collections.Generic.IDictionary @@ -13,4 +13,4 @@ Microsoft.Identity.Client.ManagedIdentity.ManagedIdentitySource.ImdsV2 = 8 -> Mi Microsoft.Identity.Client.ManagedIdentityApplicationBuilder.WithExtraQueryParameters(System.Collections.Generic.IDictionary extraQueryParameters) -> Microsoft.Identity.Client.ManagedIdentityApplicationBuilder static Microsoft.Identity.Client.ApplicationBase.ResetStateForTest() -> void Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2 -Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task \ No newline at end of file +Microsoft.Identity.Client.AuthScheme.IAuthenticationOperation2.FormatResultAsync(Microsoft.Identity.Client.AuthenticationResult authenticationResult, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task diff --git a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/AuthenticationOperationTests.cs b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/AuthenticationOperationTests.cs index fd041d1938..cb48b0ebb6 100644 --- a/tests/Microsoft.Identity.Test.Unit/PublicApiTests/AuthenticationOperationTests.cs +++ b/tests/Microsoft.Identity.Test.Unit/PublicApiTests/AuthenticationOperationTests.cs @@ -25,6 +25,7 @@ using Microsoft.Identity.Test.Unit.BrokerTests; using Microsoft.VisualStudio.TestTools.UnitTesting; using NSubstitute; +using Microsoft.Identity.Client.Extensibility; namespace Microsoft.Identity.Test.Unit.PublicApiTests { @@ -210,6 +211,11 @@ public Task FormatResultAsync(AuthenticationResult authenticationResult, Cancell return Task.CompletedTask; } + public Task ValidateCachedTokenAsync(MsalCacheValidationData cachedTokenData) + { + return Task.FromResult(true); + } + public int TelemetryTokenType => 5; // Extension public string AccessTokenType => "bearer"; } @@ -301,5 +307,163 @@ private static async Task RunSilentCallAsync( return await builder .ExecuteAsync().ConfigureAwait(false); } + + [TestMethod] + public async Task ValidateCachedTokenAsync_WhenValidationFails_CacheIsIgnoredAsync() + { + // Arrange + var authScheme = Substitute.For(); + authScheme.AuthorizationHeaderPrefix.Returns("CustomToken"); + authScheme.AccessTokenType.Returns("bearer"); + authScheme.KeyId.Returns("keyid"); + authScheme.GetTokenRequestParams().Returns(new Dictionary() { { "tokenParam", "tokenParamValue" } }); + + // Setup FormatResultAsync to just add a prefix + authScheme.WhenForAnyArgs(x => x.FormatResultAsync(default, default)) + .Do(x => ((AuthenticationResult)x[0]).AccessToken = "validated_" + ((AuthenticationResult)x[0]).AccessToken); + + // Setup ValidateCachedTokenAsync to return false (cached token is invalid) + authScheme.ValidateCachedTokenAsync(Arg.Any()) + .Returns(Task.FromResult(false)); + + using (var httpManager = new MockHttpManager()) + { + httpManager.AddInstanceDiscoveryMockHandler(); + + var cca = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) + .WithExperimentalFeatures() + .WithClientSecret(TestConstants.ClientSecret) + .WithHttpManager(httpManager) + .BuildConcrete(); + + // First request - acquire initial token + httpManager.AddTokenResponse(TokenResponseType.Valid_ClientCredentials); + + var result1 = await cca.AcquireTokenForClient(TestConstants.s_scope) + .WithAuthenticationOperation(authScheme) + .ExecuteAsync() + .ConfigureAwait(false); + + Assert.IsNotNull(result1); + Assert.IsTrue(result1.AccessToken.StartsWith("validated_")); + Assert.AreEqual(TokenSource.IdentityProvider, result1.AuthenticationResultMetadata.TokenSource); + + // Second request - validation should fail, so new token should be acquired + httpManager.AddTokenResponse(TokenResponseType.Valid_ClientCredentials); + + var result2 = await cca.AcquireTokenForClient(TestConstants.s_scope) + .WithAuthenticationOperation(authScheme) + .ExecuteAsync() + .ConfigureAwait(false); + + // Assert - validation was called and failed, so a new token was acquired + await authScheme.Received(1).ValidateCachedTokenAsync( + Arg.Any()) + .ConfigureAwait(false); + + Assert.AreEqual(TokenSource.IdentityProvider, result2.AuthenticationResultMetadata.TokenSource); + Assert.AreEqual(0, httpManager.QueueSize); + } + } + + [TestMethod] + public async Task ValidateCachedTokenAsync_WhenValidationSucceeds_CacheIsUsedAsync() + { + // Arrange + var authScheme = Substitute.For(); + authScheme.AuthorizationHeaderPrefix.Returns("CustomToken"); + authScheme.AccessTokenType.Returns("bearer"); + authScheme.KeyId.Returns("keyid"); + authScheme.GetTokenRequestParams().Returns(new Dictionary() { { "tokenParam", "tokenParamValue" } }); + + // Setup FormatResultAsync + authScheme.WhenForAnyArgs(x => x.FormatResultAsync(default, default)) + .Do(x => ((AuthenticationResult)x[0]).AccessToken = "validated_" + ((AuthenticationResult)x[0]).AccessToken); + + // Setup ValidateCachedTokenAsync to return true (cached token is valid) + authScheme.ValidateCachedTokenAsync(Arg.Any()) + .Returns(Task.FromResult(true)); + + using (var httpManager = new MockHttpManager()) + { + httpManager.AddInstanceDiscoveryMockHandler(); + + var cca = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) + .WithExperimentalFeatures() + .WithClientSecret(TestConstants.ClientSecret) + .WithHttpManager(httpManager) + .BuildConcrete(); + + // First request - acquire initial token + httpManager.AddTokenResponse(TokenResponseType.Valid_ClientCredentials); + + var result1 = await cca.AcquireTokenForClient(TestConstants.s_scope) + .WithAuthenticationOperation(authScheme) + .ExecuteAsync() + .ConfigureAwait(false); + + Assert.IsNotNull(result1); + Assert.IsTrue(result1.AccessToken.StartsWith("validated_")); + Assert.AreEqual(TokenSource.IdentityProvider, result1.AuthenticationResultMetadata.TokenSource); + + // Second request - validation should succeed, so cached token should be returned + var result2 = await cca.AcquireTokenForClient(TestConstants.s_scope) + .WithAuthenticationOperation(authScheme) + .ExecuteAsync() + .ConfigureAwait(false); + + // Assert - validation was called and succeeded, so cached token was returned + await authScheme.Received(1).ValidateCachedTokenAsync( + Arg.Any()) + .ConfigureAwait(false); + + Assert.AreEqual(TokenSource.Cache, result2.AuthenticationResultMetadata.TokenSource); + Assert.AreEqual(result1.AccessToken, result2.AccessToken); + Assert.AreEqual(0, httpManager.QueueSize); // No additional token requests + } + } + + [TestMethod] + public async Task ValidateCachedTokenAsync_WithNullCachedItem_ValidationNotCalledAsync() + { + // Arrange + var authScheme = Substitute.For(); + authScheme.AuthorizationHeaderPrefix.Returns("CustomToken"); + authScheme.AccessTokenType.Returns("bearer"); + authScheme.KeyId.Returns("keyid"); + authScheme.GetTokenRequestParams().Returns(new Dictionary() { { "tokenParam", "tokenParamValue" } }); + + authScheme.WhenForAnyArgs(x => x.FormatResultAsync(default, default)) + .Do(x => ((AuthenticationResult)x[0]).AccessToken = "validated_" + ((AuthenticationResult)x[0]).AccessToken); + + authScheme.ValidateCachedTokenAsync(Arg.Any()) + .Returns(Task.FromResult(true)); + + using (var httpManager = new MockHttpManager()) + { + httpManager.AddInstanceDiscoveryMockHandler(); + + var cca = ConfidentialClientApplicationBuilder.Create(TestConstants.ClientId) + .WithExperimentalFeatures() + .WithClientSecret(TestConstants.ClientSecret) + .WithHttpManager(httpManager) + .BuildConcrete(); + + // First request with empty cache - no cached token exists + httpManager.AddTokenResponse(TokenResponseType.Valid_ClientCredentials); + + var result = await cca.AcquireTokenForClient(TestConstants.s_scope) + .WithAuthenticationOperation(authScheme) + .ExecuteAsync() + .ConfigureAwait(false); + + // Assert - validation was never called because no cached token existed + await authScheme.DidNotReceive().ValidateCachedTokenAsync(Arg.Any()) + .ConfigureAwait(false); + + Assert.AreEqual(TokenSource.IdentityProvider, result.AuthenticationResultMetadata.TokenSource); + Assert.AreEqual(0, httpManager.QueueSize); + } + } } } From 9fad2563e743f90f06cbaad4cd952ad9de7f2ad9 Mon Sep 17 00:00:00 2001 From: trwalke Date: Wed, 12 Nov 2025 02:39:05 -0800 Subject: [PATCH 4/5] Reverting csproj changes --- .../Microsoft.Identity.Client.csproj | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj index d80d8cf17a..5c7f577e0e 100644 --- a/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj +++ b/src/client/Microsoft.Identity.Client/Microsoft.Identity.Client.csproj @@ -80,7 +80,6 @@ - @@ -93,7 +92,7 @@ - + @@ -105,7 +104,7 @@ - + @@ -162,8 +161,4 @@ - - - - - + \ No newline at end of file From 69778b78becf6cae64db6ee7975fb3d8a8ba6eb0 Mon Sep 17 00:00:00 2001 From: Bogdan Gavril Date: Thu, 13 Nov 2025 11:18:15 +0000 Subject: [PATCH 5/5] Apply suggestions from code review Co-authored-by: Gladwin Johnson <90415114+gladjohn@users.noreply.github.com> --- .../AuthScheme/IAuthenticationOperation2.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs index 104857b87a..f43c6ec052 100644 --- a/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs +++ b/src/client/Microsoft.Identity.Client/AuthScheme/IAuthenticationOperation2.cs @@ -21,7 +21,7 @@ public interface IAuthenticationOperation2 : IAuthenticationOperation /// /// Determines whether the cached token is still valid. /// - /// Data used to determine if token is valid + /// Data used to determine if token is still valid /// Task ValidateCachedTokenAsync(MsalCacheValidationData cachedTokenData); }