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 refersh"); ConfidentialClientApplication app = SetupCca(harness); var atItem = TokenCacheHelper.CreateAccessTokenItem(); UpdateATWithRefreshOn(atItem, DateTime.UtcNow - TimeSpan.FromMinutes(1), true); TokenCacheHelper.PopulateDefaultAppTokenCache(app, atItem); TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess(); Trace.WriteLine("2. Configure AAD to be unavaiable"); 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.AssertAccessCounts(1, 0); } }
public async Task TelemetryCacheRefreshTestAsync() { using (_harness = CreateTestHarness()) { _harness.HttpManager.AddInstanceDiscoveryMockHandler(); _app = PublicClientApplicationBuilder.Create(TestConstants.ClientId) .WithHttpManager(_harness.HttpManager) .WithDefaultRedirectUri() .WithLogging((lvl, msg, pii) => Trace.WriteLine($"[MSAL_LOG][{lvl}] {msg}")) .BuildConcrete(); TokenCacheHelper.PopulateCache(_app.UserTokenCacheInternal.Accessor, addSecondAt: false); Trace.WriteLine("Step 1. Acquire Token Interactive successful"); var result = await RunAcquireTokenInteractiveAsync(AcquireTokenInteractiveOutcome.Success).ConfigureAwait(false); AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenInteractive, CacheRefreshReason.NotApplicable); AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 0); Trace.WriteLine("Step 2. Acquire Token Silent successful - AT served from cache"); result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessFromCache).ConfigureAwait(false); Assert.IsNull(result.HttpRequest, "No calls are made to the token endpoint"); Trace.WriteLine("Step 3. Acquire Token Silent successful - via expired token"); UpdateATWithRefreshOn(_app.UserTokenCacheInternal.Accessor, expired: true); TokenCacheAccessRecorder cacheAccess = _app.UserTokenCache.RecordAccess(); result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh).ConfigureAwait(false); AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, CacheRefreshReason.Expired, isCacheSerialized: true); AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 1); Trace.WriteLine("Step 4. Acquire Token Silent successful - via refresh on"); UpdateATWithRefreshOn(_app.UserTokenCacheInternal.Accessor); cacheAccess = _app.UserTokenCache.RecordAccess(); result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh).ConfigureAwait(false); AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, CacheRefreshReason.ProactivelyRefreshed, isCacheSerialized: true); // Use reflection to get the value and wait till achieved HttpTelemetryManager httpTelemetryManager = (HttpTelemetryManager)_app.ServiceBundle.HttpTelemetryManager; Type httpTeleMgrType = typeof(HttpTelemetryManager); FieldInfo field = httpTeleMgrType.GetField("_successfullSilentCallCount", BindingFlags.NonPublic | BindingFlags.Instance); Assert.IsTrue(YieldTillSatisfied(() => { var actual = (int)field.GetValue(httpTelemetryManager); return(actual == 0); })); Trace.WriteLine("Step 5. Acquire Token Silent with force_refresh = true"); result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh, forceRefresh : true).ConfigureAwait(false); AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, CacheRefreshReason.ForceRefreshOrClaims, isCacheSerialized: true); Assert.IsTrue(YieldTillSatisfied(() => { var actual = (int)field.GetValue(httpTelemetryManager); return(actual == 0); })); } }
public async Task ATS_NonExpired_NeedsRefresh_AADInvalidResponse_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, DateTime.UtcNow - TimeSpan.FromMinutes(1)); TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess(); Trace.WriteLine("3. Configure AAD to respond with the typical Invalid Grant error"); AddHttpMocks(TokenResponseType.Invalid_AADAvailable, harness.HttpManager, pca: true); var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null); // Act await AssertException.TaskThrowsAsync <MsalUiRequiredException>(() => app .AcquireTokenSilent( TestConstants.s_scope.ToArray(), account) .ExecuteAsync()) .ConfigureAwait(false); } }
public async Task ClientCreds_NonExpired_NeedsRefresh_AADInvalidResponse_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"); MsalAccessTokenCacheItem atItem = TokenCacheHelper.CreateAccessTokenItem(); UpdateATWithRefreshOn(atItem, DateTime.UtcNow - TimeSpan.FromMinutes(1)); TokenCacheHelper.PopulateDefaultAppTokenCache(app, atItem); 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 AssertException.TaskThrowsAsync <MsalUiRequiredException>(() => app.AcquireTokenForClient(TestConstants.s_scope) .ExecuteAsync()) .ConfigureAwait(false); cacheAccess.AssertAccessCounts(1, 0); } }
public async Task ClientCreds_NonExpired_NeedsRefresh_ValidResponse_Async() { // Arrange using (MockHttpAndServiceBundle harness = base.CreateTestHarness()) { Trace.WriteLine("1. Setup an app with a token cache with one AT"); ConfidentialClientApplication app = SetupCca(harness); Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed"); UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1)); TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess(); Trace.WriteLine("3. Configure AAD to respond with valid token to the refresh RT flow"); AddHttpMocks(TokenResponseType.Valid, harness.HttpManager, pca: false); // Act Trace.WriteLine("4. ATS - should perform an RT refresh"); AuthenticationResult result = await app.AcquireTokenForClient(TestConstants.s_scope) .ExecuteAsync() .ConfigureAwait(false); // Assert Assert.IsNotNull(result); Assert.AreEqual(0, harness.HttpManager.QueueSize, "MSAL should have refreshed the token because the original AT was marked for refresh"); cacheAccess.AssertAccessCounts(1, 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"); ConfidentialClientApplication app = SetupCca(harness); Trace.WriteLine("2. Configure AT so that it shows it needs to be refreshed, but is also expired"); UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1), true); TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess(); Trace.WriteLine("3. Configure AAD to be unavaiable"); AddHttpMocks(TokenResponseType.Invalid_AADUnavailable, harness.HttpManager, pca: false); // 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(504, ex.StatusCode); cacheAccess.AssertAccessCounts(1, 0); } }
public async Task ATS_Expired_NeedsRefresh_AADInvalidResponse_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, but is also expired"); UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1), true); TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess(); Trace.WriteLine("3. Configure AAD to be unavaiable"); harness.HttpManager.AddAllMocks(TokenResponseType.Invalid_AADUnavailable503); harness.HttpManager.AddTokenResponse(TokenResponseType.Invalid_AADUnavailable503); var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null); // Act MsalServiceException ex = await AssertException.TaskThrowsAsync <MsalServiceException>(() => app .AcquireTokenSilent( TestConstants.s_scope.ToArray(), account) .ExecuteAsync()) .ConfigureAwait(false); Assert.IsFalse(ex is MsalUiRequiredException, "5xx exceptions do not translate to MsalUIRequired"); Assert.AreEqual(503, ex.StatusCode); } }
[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 Interactive_SSHCert_Async() { LabResponse labResponse = await LabUserHelper.GetDefaultUserAsync().ConfigureAwait(false); IPublicClientApplication pca = PublicClientApplicationBuilder .Create(labResponse.AppId) .WithRedirectUri(SeleniumWebUI.FindFreeLocalhostRedirectUri()) .Build(); TokenCacheAccessRecorder userCacheAccess = pca.UserTokenCache.RecordAccess(); Trace.WriteLine("Part 1 - Acquire an SSH cert interactively "); string jwk = CreateJwk(); AuthenticationResult result = await pca .AcquireTokenInteractive(s_scopes) .WithCustomWebUi(CreateSeleniumCustomWebUI(labResponse.User, Prompt.ForceLogin)) .WithSSHCertificateAuthenticationScheme(jwk, "key1") .WithExtraQueryParameters(GetTestSliceParams()) // TODO: remove this once feature is in PROD .ExecuteAsync(new CancellationTokenSource(_interactiveAuthTimeout).Token) .ConfigureAwait(false); userCacheAccess.AssertAccessCounts(0, 1); Assert.AreEqual("ssh-cert", result.TokenType); IAccount account = await MsalAssert.AssertSingleAccountAsync(labResponse, pca, result).ConfigureAwait(false); userCacheAccess.AssertAccessCounts(1, 1); // the assert calls GetAccounts Trace.WriteLine("Part 2 - Acquire a token silent with the same keyID - should be served from the cache"); result = await pca .AcquireTokenSilent(s_scopes, account) .WithSSHCertificateAuthenticationScheme(jwk, "key1") .WithExtraQueryParameters(GetTestSliceParams()) // TODO: remove this once feature is in PROD .ExecuteAsync(new CancellationTokenSource(_interactiveAuthTimeout).Token) .ConfigureAwait(false); userCacheAccess.AssertAccessCounts(2, 1); account = await MsalAssert.AssertSingleAccountAsync(labResponse, pca, result).ConfigureAwait(false); userCacheAccess.AssertAccessCounts(3, 1); Trace.WriteLine("Part 3 - Acquire a token silent with a different keyID - should not sbe served from the cache"); result = await pca .AcquireTokenSilent(s_scopes, account) .WithSSHCertificateAuthenticationScheme(jwk, "key2") .WithExtraQueryParameters(GetTestSliceParams()) // TODO: remove this once feature is in PROD .ExecuteAsync(new CancellationTokenSource(_interactiveAuthTimeout).Token) .ConfigureAwait(false); Assert.AreEqual("ssh-cert", result.TokenType); userCacheAccess.AssertAccessCounts(4, 2); await MsalAssert.AssertSingleAccountAsync(labResponse, pca, result).ConfigureAwait(false); }
private static void AssertCancellationToken(TokenCacheAccessRecorder cacheAccessRecorder, CancellationTokenSource cancellationTokenSource, bool write = false) { Assert.AreEqual(cancellationTokenSource.Token, cacheAccessRecorder.LastAfterAccessNotificationArgs.CancellationToken); Assert.IsFalse(default(CancellationToken) == cacheAccessRecorder.LastAfterAccessNotificationArgs.CancellationToken); Assert.AreEqual(cancellationTokenSource.Token, cacheAccessRecorder.LastBeforeAccessNotificationArgs.CancellationToken); Assert.IsFalse(default(CancellationToken) == cacheAccessRecorder.LastBeforeAccessNotificationArgs.CancellationToken); if (write) { Assert.AreEqual(cancellationTokenSource.Token, cacheAccessRecorder.LastBeforeWriteNotificationArgs.CancellationToken); Assert.IsFalse(default(CancellationToken) == cacheAccessRecorder.LastBeforeWriteNotificationArgs.CancellationToken); } }
private static void AssertCacheKey(TokenCacheAccessRecorder cacheAccess, string expectedKey) { Assert.AreEqual( expectedKey, cacheAccess.LastAfterAccessNotificationArgs.SuggestedCacheKey); Assert.AreEqual( expectedKey, cacheAccess.LastBeforeAccessNotificationArgs.SuggestedCacheKey); Assert.AreEqual( expectedKey, cacheAccess.LastBeforeWriteNotificationArgs.SuggestedCacheKey); }
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, DateTime.UtcNow - TimeSpan.FromMinutes(1)); 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); // Assert Assert.IsNotNull(result, "ATS still succeeds even though AAD is unavaible"); Assert.AreEqual(0, harness.HttpManager.QueueSize); cacheAccess.AssertAccessCounts(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); result = await app .AcquireTokenSilent( TestConstants.s_scope.ToArray(), account) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); Assert.IsNotNull(result); cacheAccess.AssertAccessCounts(2, 1); // new tokens written to cache } }
public async Task TelemetryCacheRefreshTestAsync() { using (_harness = CreateTestHarness()) { _harness.HttpManager.AddInstanceDiscoveryMockHandler(); _app = PublicClientApplicationBuilder.Create(TestConstants.ClientId) .WithHttpManager(_harness.HttpManager) .WithDefaultRedirectUri() .WithLogging((lvl, msg, pii) => Trace.WriteLine($"[MSAL_LOG][{lvl}] {msg}")) .BuildConcrete(); var tokenCacheHelper = new TokenCacheHelper(); tokenCacheHelper.PopulateCache(_app.UserTokenCacheInternal.Accessor, addSecondAt: false); Trace.WriteLine("Step 1. Acquire Token Interactive successful"); var result = await RunAcquireTokenInteractiveAsync(AcquireTokenInteractiveOutcome.Success).ConfigureAwait(false); AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenInteractive, forceRefresh: false); AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 0); // Previous_request = 2|0||| Trace.WriteLine("Step 2. Acquire Token Silent successful - AT served from cache"); result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessFromCache).ConfigureAwait(false); Assert.IsNull(result.HttpRequest, "No calls are made to the token endpoint"); Trace.WriteLine("Step 3. Acquire Token Silent successful - via expired token"); UpdateATWithRefreshOn(_app.UserTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1), true); TokenCacheAccessRecorder cacheAccess = _app.UserTokenCache.RecordAccess(); result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh).ConfigureAwait(false); AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, forceRefresh: false, isCacheSerialized: true, cacheRefresh: "1"); AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 1); // Previous_request = 2|1||| Trace.WriteLine("Step 4. Acquire Token Silent successful - via refresh on"); UpdateATWithRefreshOn(_app.UserTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1)); cacheAccess = _app.UserTokenCache.RecordAccess(); result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh).ConfigureAwait(false); AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, forceRefresh: false, isCacheSerialized: true, cacheRefresh: "2"); AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 0); // Previous_request = 2|1||| Trace.WriteLine("Step 5. Acquire Token Silent with force_refresh = true"); result = await RunAcquireTokenSilentAsync(AcquireTokenSilentOutcome.SuccessViaCacheRefresh, forceRefresh : true).ConfigureAwait(false); AssertCurrentTelemetry(result.HttpRequest, ApiIds.AcquireTokenSilent, forceRefresh: true, isCacheSerialized: true, cacheRefresh: "3"); // Current_request = 3 | ATS_ID, 1, 3 | AssertPreviousTelemetry(result.HttpRequest, expectedSilentCount: 0); // Previous_request = 2|0||| } }
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"); UpdateATWithRefreshOn(app.UserTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1)); TokenCacheAccessRecorder cacheAccess = app.UserTokenCache.RecordAccess(); Trace.WriteLine("3. Configure AAD to respond with valid token to the refresh RT flow"); AddHttpMocks(TokenResponseType.Valid, harness.HttpManager, pca: true); // 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 Assert.IsNotNull(result); Assert.AreEqual(0, harness.HttpManager.QueueSize, "MSAL should have refreshed the token because the original AT was marked for refresh"); cacheAccess.AssertAccessCounts(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); cacheAccess.AssertAccessCounts(2, 1); } }
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"); MsalAccessTokenCacheItem atItem = TokenCacheHelper.CreateAccessTokenItem(); UpdateATWithRefreshOn(atItem, DateTime.UtcNow - TimeSpan.FromMinutes(1)); TokenCacheHelper.PopulateDefaultAppTokenCache(app, atItem); TokenCacheAccessRecorder cacheAccess = app.AppTokenCache.RecordAccess(); //UpdateATWithRefreshOn(app.AppTokenCacheInternal.Accessor, DateTime.UtcNow - TimeSpan.FromMinutes(1)); 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 unavaible"); Assert.AreEqual(0, harness.HttpManager.QueueSize); cacheAccess.AssertAccessCounts(1, 0); // the refresh failed, no new data is written to the cache // Now let AAD respond with tokens harness.HttpManager.AddTokenResponse(TokenResponseType.Valid); result = await app.AcquireTokenForClient(TestConstants.s_scope) .ExecuteAsync() .ConfigureAwait(false); Assert.IsNotNull(result); cacheAccess.AssertAccessCounts(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_AADInvalidResponse_Async() { bool wasErrorLogged = false; // Arrange using (MockHttpAndServiceBundle harness = base.CreateTestHarness()) { Trace.WriteLine("1. Setup an app with a token cache with one AT"); PublicClientApplication app = SetupPca(harness, LocalLogCallback); 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 the typical Invalid Grant error"); harness.HttpManager.AddAllMocks(TokenResponseType.InvalidGrant); var account = new Account(TestConstants.s_userIdentifier, TestConstants.DisplayableId, null); await app.AcquireTokenSilent( TestConstants.s_scope.ToArray(), account) .ExecuteAsync() .ConfigureAwait(false); Assert.IsTrue(YieldTillSatisfied(() => wasErrorLogged == true)); } 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 } }