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); }
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 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 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 = 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 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 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 } }