1010using Microsoft . Identity . Client . Core ;
1111using Microsoft . Identity . Client . ManagedIdentity ;
1212using Microsoft . Identity . Client . OAuth2 ;
13+ using Microsoft . Identity . Client . PlatformsCommon . Interfaces ;
1314using Microsoft . Identity . Client . Utils ;
1415
1516namespace Microsoft . Identity . Client . Internal . Requests
@@ -18,6 +19,7 @@ internal class ManagedIdentityAuthRequest : RequestBase
1819 {
1920 private readonly AcquireTokenForManagedIdentityParameters _managedIdentityParameters ;
2021 private static readonly SemaphoreSlim s_semaphoreSlim = new SemaphoreSlim ( 1 , 1 ) ;
22+ private readonly ICryptographyManager _cryptoManager ;
2123
2224 public ManagedIdentityAuthRequest (
2325 IServiceBundle serviceBundle ,
@@ -26,28 +28,62 @@ public ManagedIdentityAuthRequest(
2628 : base ( serviceBundle , authenticationRequestParameters , managedIdentityParameters )
2729 {
2830 _managedIdentityParameters = managedIdentityParameters ;
31+ _cryptoManager = serviceBundle . PlatformProxy . CryptographyManager ;
2932 }
3033
3134 protected override async Task < AuthenticationResult > ExecuteAsync ( CancellationToken cancellationToken )
3235 {
3336 AuthenticationResult authResult = null ;
3437 ILoggerAdapter logger = AuthenticationRequestParameters . RequestContext . Logger ;
3538
36- // Skip checking cache when force refresh or claims is specified
37- if ( _managedIdentityParameters . ForceRefresh || ! string . IsNullOrEmpty ( AuthenticationRequestParameters . Claims ) )
39+ // 1. FIRST, handle ForceRefresh
40+ if ( _managedIdentityParameters . ForceRefresh )
3841 {
42+ //log a warning if Claims are also set
43+ if ( ! string . IsNullOrEmpty ( AuthenticationRequestParameters . Claims ) )
44+ {
45+ logger . Warning ( "[ManagedIdentityRequest] Both ForceRefresh and Claims are set. Using ForceRefresh to skip cache." ) ;
46+ }
47+
3948 AuthenticationRequestParameters . RequestContext . ApiEvent . CacheInfo = CacheRefreshReason . ForceRefreshOrClaims ;
40-
41- logger . Info ( "[ManagedIdentityRequest] Skipped looking for a cached access token because ForceRefresh or Claims were set. " +
42- "This means either a force refresh was requested or claims were present." ) ;
49+ logger . Info ( "[ManagedIdentityRequest] Skipped using the cache because ForceRefresh was set." ) ;
4350
51+ // Straight to the MI endpoint
4452 authResult = await GetAccessTokenAsync ( cancellationToken , logger ) . ConfigureAwait ( false ) ;
4553 return authResult ;
4654 }
4755
48- MsalAccessTokenCacheItem cachedAccessTokenItem = await GetCachedAccessTokenAsync ( ) . ConfigureAwait ( false ) ;
56+ // 2. Otherwise, look for a cached token
57+ MsalAccessTokenCacheItem cachedAccessTokenItem = await GetCachedAccessTokenAsync ( )
58+ . ConfigureAwait ( false ) ;
59+
60+ // If we have claims, we do NOT use the cached token (but we still need it to compute the hash).
61+ if ( ! string . IsNullOrEmpty ( AuthenticationRequestParameters . Claims ) )
62+ {
63+ _managedIdentityParameters . Claims = AuthenticationRequestParameters . Claims ;
64+ AuthenticationRequestParameters . RequestContext . ApiEvent . CacheInfo = CacheRefreshReason . ForceRefreshOrClaims ;
4965
50- // No access token or cached access token needs to be refreshed
66+ // If there is a cached token, compute its hash for the “revoked token” scenario
67+ if ( cachedAccessTokenItem != null )
68+ {
69+ string cachedTokenHash = _cryptoManager . CreateSha256HashHex ( cachedAccessTokenItem . Secret ) ;
70+ _managedIdentityParameters . RevokedTokenHash = cachedTokenHash ;
71+
72+ logger . Info ( "[ManagedIdentityRequest] Claims are present. Computed hash of the cached (revoked) token. " +
73+ "Will now request a fresh token from the MI endpoint." ) ;
74+ }
75+ else
76+ {
77+ logger . Info ( "[ManagedIdentityRequest] Claims are present, but no cached token was found. " +
78+ "Requesting a fresh token from the MI endpoint without a revoked-token hash." ) ;
79+ }
80+
81+ // In both cases, we skip using the cached token and get a new one
82+ authResult = await GetAccessTokenAsync ( cancellationToken , logger ) . ConfigureAwait ( false ) ;
83+ return authResult ;
84+ }
85+
86+ // 3. If we have no ForceRefresh and no claims, we can use the cache
5187 if ( cachedAccessTokenItem != null )
5288 {
5389 authResult = CreateAuthenticationResultFromCache ( cachedAccessTokenItem ) ;
@@ -67,30 +103,33 @@ protected override async Task<AuthenticationResult> ExecuteAsync(CancellationTok
67103
68104 SilentRequestHelper . ProcessFetchInBackground (
69105 cachedAccessTokenItem ,
70- ( ) =>
71- {
72- // Use a linked token source, in case the original cancellation token source is disposed before this background task completes.
73- using var tokenSource = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
74- return GetAccessTokenAsync ( tokenSource . Token , logger ) ;
75- } , logger , ServiceBundle , AuthenticationRequestParameters . RequestContext . ApiEvent ,
106+ ( ) =>
107+ {
108+ // Use a linked token source, in case the original cancellation token source is disposed before this background task completes.
109+ using var tokenSource = CancellationTokenSource . CreateLinkedTokenSource ( cancellationToken ) ;
110+ return GetAccessTokenAsync ( tokenSource . Token , logger ) ;
111+ } , logger , ServiceBundle , AuthenticationRequestParameters . RequestContext . ApiEvent ,
76112 AuthenticationRequestParameters . RequestContext . ApiEvent . CallerSdkApiId ,
77113 AuthenticationRequestParameters . RequestContext . ApiEvent . CallerSdkVersion ) ;
78114 }
79115 }
80116 catch ( MsalServiceException e )
81117 {
118+ // If background refresh fails, we handle the exception
82119 return await HandleTokenRefreshErrorAsync ( e , cachedAccessTokenItem ) . ConfigureAwait ( false ) ;
83120 }
84121 }
85122 else
86123 {
87- // No AT in the cache
124+ // No cached token
88125 if ( AuthenticationRequestParameters . RequestContext . ApiEvent . CacheInfo != CacheRefreshReason . Expired )
89126 {
90127 AuthenticationRequestParameters . RequestContext . ApiEvent . CacheInfo = CacheRefreshReason . NoCachedAccessToken ;
91128 }
92129
93- logger . Info ( "[ManagedIdentityRequest] No cached access token. Getting a token from the managed identity endpoint." ) ;
130+ logger . Info ( "[ManagedIdentityRequest] No cached access token found. " +
131+ "Getting a token from the managed identity endpoint." ) ;
132+
94133 authResult = await GetAccessTokenAsync ( cancellationToken , logger ) . ConfigureAwait ( false ) ;
95134 }
96135
@@ -112,12 +151,15 @@ private async Task<AuthenticationResult> GetAccessTokenAsync(
112151
113152 try
114153 {
115- // Bypass cache and send request to token endpoint, when
116- // 1. Force refresh is requested, or
117- // 2. If the access token needs to be refreshed proactively.
154+ // While holding the semaphore, decide whether to bypass the cache.
155+ // Re-check because another thread may have filled the cache while we waited.
156+ // Bypass when:
157+ // 1) ForceRefresh is requested
158+ // 2) Proactive refresh is in effect
159+ // 3) Claims are present (revocation flow)
118160 if ( _managedIdentityParameters . ForceRefresh ||
119161 AuthenticationRequestParameters . RequestContext . ApiEvent . CacheInfo == CacheRefreshReason . ProactivelyRefreshed ||
120- ! string . IsNullOrEmpty ( AuthenticationRequestParameters . Claims ) )
162+ ! string . IsNullOrEmpty ( _managedIdentityParameters . Claims ) )
121163 {
122164 authResult = await SendTokenRequestForManagedIdentityAsync ( logger , cancellationToken ) . ConfigureAwait ( false ) ;
123165 }
0 commit comments