public async Task AcquireTokenByOboMissMatchUserAssertionsAsync() { using (var httpManager = new MockHttpManager()) { httpManager.AddInstanceDiscoveryMockHandler(); AddMockHandlerAadSuccess(httpManager, TestConstants.AuthorityCommonTenant); var cca = ConfidentialClientApplicationBuilder .Create(TestConstants.ClientId) .WithClientSecret(TestConstants.ClientSecret) .WithAuthority(TestConstants.AuthorityCommonTenant) .WithHttpManager(httpManager) .BuildConcrete(); UserAssertion userAssertion = new UserAssertion(TestConstants.DefaultAccessToken); var result = await cca.AcquireTokenOnBehalfOf(TestConstants.s_scope, userAssertion).ExecuteAsync().ConfigureAwait(false); Assert.IsNotNull(result); Assert.AreEqual("some-access-token", result.AccessToken); //Update user assertions TokenCacheHelper.UpdateUserAssertions(cca); MockHttpMessageHandler mockTokenRequestHttpHandlerRefresh = AddMockHandlerAadSuccess(httpManager, TestConstants.AuthorityCommonTenant); //Access and refresh tokens are have a different user assertion so MSAL should perform OBO. result = await cca.AcquireTokenOnBehalfOf(TestConstants.s_scope, userAssertion).ExecuteAsync().ConfigureAwait(false); Assert.IsNotNull(result); Assert.AreEqual("some-access-token", result.AccessToken); Assert.AreEqual(result.AuthenticationResultMetadata.TokenSource, TokenSource.IdentityProvider); } }
//Since this test performs a large number of operations it should not be rerun on other clouds. private async Task RunOnBehalfOfTestWithTokenCacheAsync(LabResponse labResponse) { LabUser user = labResponse.User; string oboHost; string secret; string authority; string publicClientID; string confidentialClientID; string[] oboScope; oboHost = PublicCloudHost; secret = _keyVault.GetSecret(TestConstants.MsalOBOKeyVaultUri).Value; authority = TestConstants.AuthorityOrganizationsTenant; publicClientID = PublicCloudPublicClientIDOBO; confidentialClientID = PublicCloudConfidentialClientIDOBO; oboScope = s_publicCloudOBOServiceScope; //TODO: acquire scenario specific client ids from the lab response SecureString securePassword = new NetworkCredential("", user.GetOrFetchPassword()).SecurePassword; var factory = new HttpSnifferClientFactory(); var msalPublicClient = PublicClientApplicationBuilder.Create(publicClientID) .WithAuthority(authority) .WithRedirectUri(TestConstants.RedirectUri) .WithTestLogging() .WithHttpClientFactory(factory) .Build(); var authResult = await msalPublicClient.AcquireTokenByUsernamePassword(oboScope, user.Upn, securePassword) .ExecuteAsync() .ConfigureAwait(false); var confidentialApp = ConfidentialClientApplicationBuilder .Create(confidentialClientID) .WithAuthority(new Uri(oboHost + authResult.TenantId), true) .WithClientSecret(secret) .WithTestLogging() .BuildConcrete(); var userCacheRecorder = confidentialApp.UserTokenCache.RecordAccess(); UserAssertion userAssertion = new UserAssertion(authResult.AccessToken); string atHash = userAssertion.AssertionHash; authResult = await confidentialApp.AcquireTokenOnBehalfOf(s_scopes, userAssertion) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); MsalAssert.AssertAuthResult(authResult, user); Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey); Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource); //Run OBO again. Should get token from cache authResult = await confidentialApp.AcquireTokenOnBehalfOf(s_scopes, userAssertion) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); Assert.IsNotNull(authResult); Assert.IsNotNull(authResult.AccessToken); Assert.IsNotNull(authResult.IdToken); Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache); Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens); Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey); Assert.AreEqual(TokenSource.Cache, authResult.AuthenticationResultMetadata.TokenSource); //Expire access tokens TokenCacheHelper.ExpireAllAccessTokens(confidentialApp.UserTokenCacheInternal); //Run OBO again. Should do OBO flow since the AT is expired and RTs aren't cached for normal OBO flow authResult = await confidentialApp.AcquireTokenOnBehalfOf(s_scopes, userAssertion) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); Assert.IsNotNull(authResult); Assert.IsNotNull(authResult.AccessToken); Assert.IsNotNull(authResult.IdToken); Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache); Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens); Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey); Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource); AssertLastHttpContent("on_behalf_of"); //creating second app with no refresh tokens var atItems = confidentialApp.UserTokenCacheInternal.Accessor.GetAllAccessTokens(); var confidentialApp2 = ConfidentialClientApplicationBuilder .Create(confidentialClientID) .WithAuthority(new Uri(oboHost + authResult.TenantId), true) .WithClientSecret(secret) .WithTestLogging() .WithHttpClientFactory(factory) .BuildConcrete(); TokenCacheHelper.ExpireAccessToken(confidentialApp2.UserTokenCacheInternal, atItems.FirstOrDefault()); //Should perform OBO flow since the access token is expired and the refresh token does not exist authResult = await confidentialApp2.AcquireTokenOnBehalfOf(s_scopes, userAssertion) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); Assert.IsNotNull(authResult); Assert.IsNotNull(authResult.AccessToken); Assert.IsNotNull(authResult.IdToken); Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache); Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens); Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey); Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource); AssertLastHttpContent("on_behalf_of"); TokenCacheHelper.ExpireAllAccessTokens(confidentialApp2.UserTokenCacheInternal); TokenCacheHelper.UpdateUserAssertions(confidentialApp2); //Should perform OBO flow since the access token and the refresh token contains the wrong user assertion hash authResult = await confidentialApp2.AcquireTokenOnBehalfOf(s_scopes, userAssertion) .ExecuteAsync(CancellationToken.None) .ConfigureAwait(false); Assert.IsNotNull(authResult); Assert.IsNotNull(authResult.AccessToken); Assert.IsNotNull(authResult.IdToken); Assert.IsTrue(!userCacheRecorder.LastAfterAccessNotificationArgs.IsApplicationCache); Assert.IsTrue(userCacheRecorder.LastAfterAccessNotificationArgs.HasTokens); Assert.AreEqual(atHash, userCacheRecorder.LastAfterAccessNotificationArgs.SuggestedCacheKey); Assert.AreEqual(TokenSource.IdentityProvider, authResult.AuthenticationResultMetadata.TokenSource); AssertLastHttpContent("on_behalf_of"); }