@@ -169,6 +169,71 @@ public async Task LongRunningThenNormalObo_WithTheSameKey_TestAsync()
169
169
Assert . AreEqual ( 1 , cca . UserTokenCacheInternal . Accessor . GetAllRefreshTokens ( ) . Count ) ;
170
170
}
171
171
172
+ [ TestMethod ]
173
+ public async Task InitiateLRWithCustomKey_ThenAcquireLRWithSameKey_Succeeds_TestAsync ( )
174
+ {
175
+ // Arrange
176
+ LabUser user1 = ( await LabUserHelper . GetSpecificUserAsync ( "[email protected] " ) . ConfigureAwait ( false ) ) . User ;
177
+ IPublicClientApplication pca = PublicClientApplicationBuilder
178
+ . Create ( PublicClientID )
179
+ . WithAuthority ( AadAuthorityAudience . AzureAdMultipleOrgs )
180
+ . Build ( ) ;
181
+
182
+ // Acquire a token for the user via user name/password
183
+ AuthenticationResult userAuthResult = await pca
184
+ . AcquireTokenByUsernamePassword ( s_oboServiceScope , user1 . Upn , user1 . GetOrFetchPassword ( ) )
185
+ . ExecuteAsync ( )
186
+ . ConfigureAwait ( false ) ;
187
+
188
+ // Build the ConfidentialClient for OBO
189
+ ConfidentialClientApplication cca = BuildCCA ( userAuthResult . TenantId ) ;
190
+
191
+ // We'll use a *non-empty* custom key (NOT null, NOT empty).
192
+ // In raw MSAL, this means MSAL *will NOT* overwrite it with the assertion hash.
193
+ string oboCacheKey = "MyCustomKey" ;
194
+
195
+ // Act #1: Initiate the long running session.
196
+ // MSAL associates the "MyCustomKey" partition in its cache with the new LR token.
197
+ AuthenticationResult initiateResult = await cca
198
+ . InitiateLongRunningProcessInWebApi ( s_scopes , userAuthResult . AccessToken , ref oboCacheKey )
199
+ . ExecuteAsync ( )
200
+ . ConfigureAwait ( false ) ;
201
+
202
+ // Assert #1: MSAL does NOT overwrite a non-empty key with the assertion hash.
203
+ // The user-provided key remains "MyCustomKey"
204
+ Assert . AreEqual ( "MyCustomKey" , oboCacheKey ,
205
+ "Expected MSAL to respect custom OBO key exactly, not to overwrite it." ) ;
206
+
207
+ // Act #2: Acquire a token from that same long-running process,
208
+ // re-using the same custom key but passing *no* user assertion.
209
+ // Because we are in the same session, MSAL should find the token in
210
+ // the LR OBO partition or use the RT if the AT is expired.
211
+ AuthenticationResult lrAcquireResult = await cca
212
+ . AcquireTokenInLongRunningProcess ( s_scopes , oboCacheKey )
213
+ . ExecuteAsync ( )
214
+ . ConfigureAwait ( false ) ;
215
+
216
+ // Assert #2: We should get a valid token and no exception
217
+ Assert . IsNotNull ( lrAcquireResult . AccessToken ,
218
+ "AcquireTokenInLongRunningProcess should succeed using the same custom key." ) ;
219
+ Assert . AreNotEqual ( "" , lrAcquireResult . AccessToken ) ;
220
+
221
+ // Force an RT flow by expiring ALL access tokens
222
+ TokenCacheHelper . ExpireAllAccessTokens ( cca . UserTokenCacheInternal ) ;
223
+
224
+ // Act #3 – call again; MSAL must redeem the RT
225
+ AuthenticationResult lrAcquireAfterExpiry = await cca
226
+ . AcquireTokenInLongRunningProcess ( s_scopes , oboCacheKey )
227
+ . ExecuteAsync ( )
228
+ . ConfigureAwait ( false ) ;
229
+
230
+ // Assert #3 - call should succeed after RT redemption.
231
+ Assert . IsFalse ( string . IsNullOrEmpty ( lrAcquireAfterExpiry . AccessToken ) ,
232
+ "Second call should succeed after RT redemption." ) ;
233
+ Assert . AreNotEqual ( lrAcquireResult . AccessToken , lrAcquireAfterExpiry . AccessToken ,
234
+ "Access token should differ, proving MSAL used the refresh-token path." ) ;
235
+ }
236
+
172
237
/// <summary>
173
238
/// Tests the behavior when calling both, long-running and normal OBO methods.
174
239
/// Both methods should return the same tokens, since the cache key is the same.
0 commit comments