[Ignore] // unstable, see https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/2918 public async Task ATS_NonExpired_NeedsRefresh_ValidResponse_Async() { // Arrange using (MockHttpAndServiceBundle harness = base.CreateTestHarness()) { Trace.WriteLine("1. Setup an app with a token cache with one AT"); PublicClientApplication app = SetupPca(harness); Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed"); var refreshOn = UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor).RefreshOn; TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess(); Trace.WriteLine("3. Configure AAD to respond with valid token to the refresh RT flow"); harness.HttpManager.AddAllMocks(TokenResponseType.Valid_UserFlows); // Act Trace.WriteLine("4. ATS - should perform an RT refresh"); var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null); AuthenticationResult result = await app .AcquireTokenSilent( TestConstants.s_scope.ToArray(), account) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); // Assert token is proactivly refreshed Assert.IsNotNull(result); Assert.IsTrue(result.AuthenticationResultMetadata.CacheRefreshReason == CacheRefreshReason.ProactivelyRefreshed); Assert.IsTrue(result.AuthenticationResultMetadata.RefreshOn == refreshOn); // The following can be indeterministic due to background threading nature // So it is verified on check and wait basis YieldTillSatisfied(() => harness.HttpManager.QueueSize == 0); Assert.AreEqual(0, harness.HttpManager.QueueSize, "MSAL should have refreshed the token because the original AT was marked for refresh"); cacheAccess.WaitTo_AssertAcessCounts(1, 1); MsalAccessTokenCacheItem ati = app.UserTokenCacheInternal.Accessor.GetAllAccessTokens().Single(); Assert.IsTrue(ati.RefreshOn > DateTime.UtcNow + TimeSpan.FromMinutes(10)); // New ATS does not refresh the AT because RefreshIn is in the future Trace.WriteLine("5. ATS - should fetch from the cache only"); result = await app .AcquireTokenSilent( TestConstants.s_scope.ToArray(), account) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); Assert.IsNotNull(result); Assert.IsTrue(result.AuthenticationResultMetadata.RefreshOn == ati.RefreshOn); Assert.IsTrue(result.AuthenticationResultMetadata.CacheRefreshReason == CacheRefreshReason.NotApplicable); cacheAccess.WaitTo_AssertAcessCounts(2, 1); } }
public async Task ClientCreds_Expired_NeedsRefresh_AADInvalidResponse_Async() { // Arrange using (MockHttpAndServiceBundle harness = base.CreateTestHarness()) { Trace.WriteLine("1. Setup an app with a token cache with one AT = expired and needs refresh"); ConfidentialClientApplication app = SetupCca(harness); UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor, expired: true); TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess(); Trace.WriteLine("2. Configure AAD to be unavailable"); harness.HttpManager.AddAllMocks(TokenResponseType.Invalid_AADUnavailable503); harness.HttpManager.AddTokenResponse(TokenResponseType.Invalid_AADUnavailable503); // Act MsalServiceException ex = await AssertException.TaskThrowsAsync <MsalServiceException>(() => app .AcquireTokenForClient(TestConstants.s_scope) .ExecuteAsync()) .ConfigureAwait(false); Assert.IsFalse(ex is MsalUiRequiredException, "5xx exceptions do not translate to MsalUIRequired"); Assert.AreEqual(503, ex.StatusCode); cacheAccess.WaitTo_AssertAcessCounts(1, 0); } }
public async Task ClientCreds_NonExpired_NeedsRefresh_AADUnavailableResponse_Async() { // Arrange using (MockHttpAndServiceBundle harness = base.CreateTestHarness()) { Trace.WriteLine("1. Setup an app "); ConfidentialClientApplication app = SetupCca(harness); Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed"); UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor); TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess(); Trace.WriteLine("3. Configure AAD to respond with an error"); harness.HttpManager.AddAllMocks(TokenResponseType.Invalid_AADUnavailable503); harness.HttpManager.AddTokenResponse(TokenResponseType.Invalid_AADUnavailable503); // Act AuthenticationResult result = await app.AcquireTokenForClient(TestConstants.s_scope) .ExecuteAsync() .ConfigureAwait(false); // Assert Assert.IsNotNull(result, "ClientCreds should still succeeds even though AAD is unavailable"); YieldTillSatisfied(() => harness.HttpManager.QueueSize == 0); Assert.AreEqual(0, harness.HttpManager.QueueSize); cacheAccess.WaitTo_AssertAcessCounts(1, 0); // the refresh failed, no new data is written to the cache // Now let AAD respond with tokens harness.HttpManager.AddTokenResponse(TokenResponseType.Valid_ClientCredentials); result = await app.AcquireTokenForClient(TestConstants.s_scope) .ExecuteAsync() .ConfigureAwait(false); Assert.IsNotNull(result); cacheAccess.WaitTo_AssertAcessCounts(2, 1); // new tokens written to cache } }
public async Task ClientCreds_NonExpired_NeedsRefresh_ValidResponse_Async() { // Arrange using (MockHttpAndServiceBundle harness = base.CreateTestHarness()) { Trace.WriteLine("1. Setup an app"); ConfidentialClientApplication app = SetupCca(harness); Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed"); var refreshOn = UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor).RefreshOn; TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess(); Trace.WriteLine("3. Configure AAD to respond with valid token to the refresh RT flow"); harness.HttpManager.AddAllMocks(TokenResponseType.Valid_ClientCredentials); // Act Trace.WriteLine("4. ATS - should perform an RT refresh"); AuthenticationResult result = await app.AcquireTokenForClient(TestConstants.s_scope) .ExecuteAsync() .ConfigureAwait(false); // Assert YieldTillSatisfied(() => harness.HttpManager.QueueSize == 0); Assert.IsNotNull(result); Assert.AreEqual(0, harness.HttpManager.QueueSize, "MSAL should have refreshed the token because the original AT was marked for refresh"); cacheAccess.WaitTo_AssertAcessCounts(1, 1); Assert.IsTrue(result.AuthenticationResultMetadata.CacheRefreshReason == CacheRefreshReason.ProactivelyRefreshed); Assert.IsTrue(result.AuthenticationResultMetadata.RefreshOn == refreshOn); result = await app.AcquireTokenForClient(TestConstants.s_scope) .ExecuteAsync() .ConfigureAwait(false); Assert.IsTrue(result.AuthenticationResultMetadata.CacheRefreshReason == CacheRefreshReason.NotApplicable); } }
public async Task ClientCreds_NonExpired_NeedsRefresh_AADInvalidResponse_Async() { bool wasErrorLogged = false; // Arrange using (MockHttpAndServiceBundle harness = base.CreateTestHarness()) { Trace.WriteLine("1. Setup an app"); ConfidentialClientApplication app = SetupCca(harness, LocalLogCallback); Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed"); UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor); TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess(); Trace.WriteLine("3. Configure AAD to respond with the typical Invalid Grant error"); harness.HttpManager.AddAllMocks(TokenResponseType.InvalidGrant); // Act await app.AcquireTokenForClient(TestConstants.s_scope) .ExecuteAsync() .ConfigureAwait(false); Assert.IsTrue(YieldTillSatisfied(() => wasErrorLogged == true)); cacheAccess.WaitTo_AssertAcessCounts(1, 0); } void LocalLogCallback(LogLevel level, string message, bool containsPii) { if (level == LogLevel.Error && message.Contains(BackgroundFetch_Failed)) { wasErrorLogged = true; } } }
public async Task ATS_NonExpired_NeedsRefresh_AADUnavailableResponse_Async() { // Arrange using (MockHttpAndServiceBundle harness = base.CreateTestHarness()) { Trace.WriteLine("1. Setup an app with a token cache with one AT"); PublicClientApplication app = SetupPca(harness); Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed"); UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor); TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess(); Trace.WriteLine("3. Configure AAD to respond with a 500 error"); harness.HttpManager.AddAllMocks(TokenResponseType.Invalid_AADUnavailable503); harness.HttpManager.AddTokenResponse(TokenResponseType.Invalid_AADUnavailable503); // Act var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null); AuthenticationResult result = await app .AcquireTokenSilent( TestConstants.s_scope.ToArray(), account) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); // The following can be indeterministic due to background threading nature // So it is verified on check and wait basis Assert.IsTrue(YieldTillSatisfied(() => harness.HttpManager.QueueSize == 0), "Background refresh 1 did not execute."); // Assert Assert.AreEqual(0, harness.HttpManager.QueueSize); Assert.AreEqual(CacheRefreshReason.ProactivelyRefreshed, result.AuthenticationResultMetadata.CacheRefreshReason); cacheAccess.WaitTo_AssertAcessCounts(1, 0); // the refresh failed, no new data is written to the cache // reset throttling, otherwise MSAL would block similar requests for 2 minutes // and we would still get a cached response SingletonThrottlingManager.GetInstance().ResetCache(); // Now let AAD respond with tokens harness.HttpManager.AddTokenResponse(TokenResponseType.Valid_UserFlows); result = await app .AcquireTokenSilent( TestConstants.s_scope.ToArray(), account) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); Assert.AreEqual(CacheRefreshReason.ProactivelyRefreshed, result.AuthenticationResultMetadata.CacheRefreshReason); Assert.IsTrue(YieldTillSatisfied( () => harness.HttpManager.QueueSize == 0), "Background refresh 2 did not execute."); Assert.IsTrue( YieldTillSatisfied(() => cacheAccess.AfterAccessTotalCount == 3), "The background refresh executed, but the cache was not updated"); cacheAccess.WaitTo_AssertAcessCounts(2, 1); // new tokens written to cache } }