// only works for AAD plugin. MSA plugin does not allow for privacy reasons private async Task <MsalTokenResponse> AcquireInteractiveWithAadBrowserAsync( AuthenticationRequestParameters authenticationRequestParameters, Prompt msalPrompt) { var provider = await _webAccountProviderFactory.GetAccountProviderAsync( authenticationRequestParameters.Authority.TenantId).ConfigureAwait(true); WebTokenRequest webTokenRequest = await _aadPlugin.CreateWebTokenRequestAsync( provider, authenticationRequestParameters, isForceLoginPrompt : true, isInteractive : true, isAccountInWam : false) .ConfigureAwait(false); WamAdapters.AddMsalParamsToRequest(authenticationRequestParameters, webTokenRequest, _logger); AddPromptToRequest(msalPrompt, true, webTokenRequest); var wamResult = await _wamProxy.RequestTokenForWindowAsync( _parentHandle, webTokenRequest).ConfigureAwait(false); return(WamAdapters.CreateMsalResponseFromWamResponse( wamResult, _aadPlugin, authenticationRequestParameters.AppConfig.ClientId, _logger, isInteractive: true)); }
/// <summary> /// The algorithm here is much more complex in order to workaround a limitation in the AAD plugin's /// handling of guest accounts: /// /// 1. Read the accounts from WAM.AADPlugin /// 2. For each account, we need to find its home_account_id as the one from WAM may not be correct /// 3. If we can find a cached account with the same LocalAccountId or UPN, use it /// 4. If not, make a simple silent token request and use the client info provided /// </summary> public async Task <IReadOnlyList <IAccount> > GetAccountsAsync( string clientId, AuthorityInfo authorityInfo, Cache.ICacheSessionManager cacheSessionManager, Instance.Discovery.IInstanceDiscoveryManager instanceDiscoveryManager) { var webAccountProvider = await _webAccountProviderFactory.GetAccountProviderAsync("organizations").ConfigureAwait(false); var wamAccounts = await _wamProxy.FindAllWebAccountsAsync(webAccountProvider, clientId).ConfigureAwait(false); if (wamAccounts.Count > 0) { var webAccountEnvs = wamAccounts .Select(w => { _wamProxy.TryGetAccountProperty(w, "Authority", out string accountAuthority); if (accountAuthority != null) { return((new Uri(accountAuthority)).Host); } else { _logger.WarningPii( $"[WAM AAD Provider] Could not convert the WAM account {w.UserName} (id: {w.Id}) to an MSAL account because the Authority could not be found", $"[WAM AAD Provider] Could not convert the WAM account {w.Id} to an MSAL account because the Authority could not be found"); return(null); } }) .Where(a => a != null); var instanceMetadata = await instanceDiscoveryManager.GetMetadataEntryTryAvoidNetworkAsync(authorityInfo, webAccountEnvs, cacheSessionManager.RequestContext) .ConfigureAwait(false); var accountsFromCache = await cacheSessionManager.GetAccountsAsync().ConfigureAwait(false); var msalAccountTasks = wamAccounts .Select( async webAcc => await ConvertToMsalAccountOrNullAsync( clientId, webAcc, instanceMetadata, cacheSessionManager, accountsFromCache).ConfigureAwait(false)); var msalAccounts = (await Task.WhenAll(msalAccountTasks).ConfigureAwait(false)).Where(a => a != null).ToList(); _logger.Info($"[WAM AAD Provider] GetAccountsAsync converted {msalAccounts.Count} accounts from {wamAccounts.Count} WAM accounts"); return(msalAccounts); } _logger.Info("[WAM AAD provider] No accounts found."); return(Array.Empty <IAccount>()); }
private async Task <WebAccountProvider> GetProviderAsync( string authority, bool isMsa) { WebAccountProvider provider; string tenantOrAuthority = isMsa ? "consumers" : authority; provider = await _webAccountProviderFactory.GetAccountProviderAsync(tenantOrAuthority) .ConfigureAwait(false); return(provider); }
/// <summary> /// In WAM, AcquireTokenInteractive is always associated to an account. WAM also allows for an "account picker" to be displayed, /// which is similar to the EVO browser experience, allowing the user to add an account or use an existing one. /// /// MSAL does not have a concept of account picker so MSAL.AccquireTokenInteractive will: /// /// 1. Call WAM.AccountPicker if an IAccount (or possibly login_hint) is not configured /// 2. Figure out the WAM.AccountID associated to the MSAL.Account /// 3. Call WAM.AcquireTokenInteractive with the WAM.AccountID /// /// To make matters more complicated, WAM has 2 plugins - AAD and MSA. With AAD plugin, /// it is possible to list all WAM accounts and find the one associated to the MSAL account. /// However, MSA plugin does NOT allow listing of accounts, and the only way to figure out the /// WAM account ID is to use the account picker. This makes AcquireTokenSilent impossible for MSA, /// because we would not be able to map an Msal.Account to a WAM.Account. To overcome this, /// we save the WAM.AccountID in MSAL's cache. /// </summary> public async Task <MsalTokenResponse> AcquireTokenInteractiveAsync( AuthenticationRequestParameters authenticationRequestParameters, AcquireTokenInteractiveParameters acquireTokenInteractiveParameters) { if (_synchronizationContext == null) { throw new MsalClientException( MsalError.WamUiThread, "AcquireTokenInteractive with broker must be called from the UI thread when using WAM. " + "Note that console applications are not currently supported in conjuction with WAM." + ErrorMessageSuffix); } if (authenticationRequestParameters.Account != null || !string.IsNullOrEmpty(authenticationRequestParameters.LoginHint)) { bool isMsa = IsMsaRequest( authenticationRequestParameters.Authority, authenticationRequestParameters?.Account?.HomeAccountId?.TenantId, // TODO: we could furher optimize here by searching for an account based on UPN IsMsaPassthrough(authenticationRequestParameters)); IWamPlugin wamPlugin = isMsa ? _msaPlugin : _aadPlugin; WebAccountProvider provider; if (isMsa) { provider = await _webAccountProviderFactory.GetAccountProviderAsync("consumers").ConfigureAwait(false); } else { provider = await _webAccountProviderFactory.GetAccountProviderAsync(authenticationRequestParameters.Authority.AuthorityInfo.CanonicalAuthority) .ConfigureAwait(false); } var wamAccount = await FindWamAccountForMsalAccountAsync( provider, wamPlugin, authenticationRequestParameters.Account, authenticationRequestParameters.LoginHint, authenticationRequestParameters.ClientId).ConfigureAwait(false); if (wamAccount != null) { var wamResult = await AcquireInteractiveWithoutPickerAsync( authenticationRequestParameters, acquireTokenInteractiveParameters.Prompt, wamPlugin, provider, wamAccount) .ConfigureAwait(false); return(CreateMsalTokenResponse(wamResult, wamPlugin, isInteractive: true)); } } return(await AcquireInteractiveWithPickerAsync( authenticationRequestParameters) .ConfigureAwait(false)); }
/// <summary> /// Generally the MSA plugin will NOT return the accounts back to the app. This is due /// to privacy concerns. However, some test apps are allowed to do this, hence the code. /// Normal 1st and 3rd party apps must use AcquireTokenInteractive to login first, and then MSAL will /// save the account for later use. /// </summary> public async Task <IEnumerable <IAccount> > GetAccountsAsync(string clientID) { var webAccounProvider = await _webAccountProviderFactory.GetAccountProviderAsync("consumers").ConfigureAwait(false); var webAccounts = await _wamProxy.FindAllWebAccountsAsync(webAccounProvider, clientID).ConfigureAwait(false); var msalAccounts = webAccounts .Select(webAcc => ConvertToMsalAccountOrNull(webAcc)) .Where(a => a != null) .ToList(); _logger.Info($"[WAM MSA Plugin] GetAccountsAsync converted {webAccounts.Count()} MSAL accounts"); return(msalAccounts); }
public async Task GetAccounts_WamAccounts_NoAuthority_Async() { // Arrange using (MockHttpAndServiceBundle harness = CreateTestHarness()) { var wamAccountProvider = new WebAccountProvider("id", "*****@*****.**", null); _webAccountProviderFactory.GetAccountProviderAsync("organizations").Returns(wamAccountProvider); var wamAccount = new WebAccount(wamAccountProvider, "*****@*****.**", WebAccountState.Connected); // no authority ... skip these accounts _wamProxy.FindAllWebAccountsAsync(wamAccountProvider, TestConstants.ClientId).Returns(new[] { wamAccount }); // Act var accounts = await _aadPlugin.GetAccountsAsync( TestConstants.ClientId, AuthorityInfo.FromAuthorityUri(TestConstants.AuthorityCommonTenant, true), _cacheSessionManager, _instanceDiscoveryManager).ConfigureAwait(false); // Assert Assert.AreEqual(0, accounts.Count()); } }
/// <summary> /// Generally the MSA plugin will NOT return the accounts back to the app. This is due /// to privacy concerns. However, some test apps are allowed to do this, hence the code. /// Normal 1st and 3rd party apps must use AcquireTokenInteractive to login first, and then MSAL will /// save the account for later use. /// </summary> public async Task <IReadOnlyList <IAccount> > GetAccountsAsync( string clientID, string authority, ICacheSessionManager cacheSessionManager, IInstanceDiscoveryManager instanceDiscoveryManager) { var webAccounProvider = await _webAccountProviderFactory.GetAccountProviderAsync("consumers").ConfigureAwait(false); var webAccounts = await _wamProxy.FindAllWebAccountsAsync(webAccounProvider, clientID).ConfigureAwait(false); var msalAccounts = webAccounts .Select(webAcc => ConvertToMsalAccountOrNull(webAcc)) .Where(a => a != null) .ToList(); _logger.Info($"[WAM MSA Plugin] GetAccountsAsync converted {webAccounts.Count} MSAL accounts"); return(msalAccounts); }
public async Task ATS_AccountWithWamId_Async() { // Arrange using (MockHttpAndServiceBundle harness = CreateTestHarness()) { _webAccountProviderFactory.ClearReceivedCalls(); var wamAccountProvider = new WebAccountProvider("id", "*****@*****.**", null); var extraQP = new Dictionary <string, string>() { { "extraQp1", "extraVal1" }, { "instance_aware", "true" } }; var requestParams = harness.CreateAuthenticationRequestParameters( TestConstants.AuthorityHomeTenant, extraQueryParameters: extraQP, validateAuthority: true); // AAD requestParams.UserConfiguredAuthority = Authority.CreateAuthority("https://login.microsoftonline.com/organizations"); requestParams.Account = new Account( $"{TestConstants.Uid}.{TestConstants.Utid}", TestConstants.DisplayableId, null, new Dictionary <string, string>() { { TestConstants.ClientId, "wam_id_1" } }); // account has wam_id! var webAccount = new WebAccount(wamAccountProvider, "*****@*****.**", WebAccountState.Connected); var webTokenRequest = new WebTokenRequest(wamAccountProvider); var webTokenResponseWrapper = Substitute.For <IWebTokenRequestResultWrapper>(); webTokenResponseWrapper.ResponseStatus.Returns(WebTokenRequestStatus.Success); var webTokenResponse = new WebTokenResponse(); webTokenResponseWrapper.ResponseData.Returns(new List <WebTokenResponse>() { webTokenResponse }); _webAccountProviderFactory.GetAccountProviderAsync(null).ReturnsForAnyArgs(Task.FromResult(wamAccountProvider)); _wamProxy.FindAccountAsync(Arg.Any <WebAccountProvider>(), "wam_id_1").Returns(Task.FromResult(webAccount)); _aadPlugin.CreateWebTokenRequestAsync( wamAccountProvider, requestParams, isForceLoginPrompt: false, isAccountInWam: true, isInteractive: false) .Returns(Task.FromResult(webTokenRequest)); var atsParams = new AcquireTokenSilentParameters(); _wamProxy.GetTokenSilentlyAsync(webAccount, webTokenRequest). Returns(Task.FromResult(webTokenResponseWrapper)); _aadPlugin.ParseSuccesfullWamResponse(webTokenResponse).Returns(_msalTokenResponse); // Act var result = await _wamBroker.AcquireTokenSilentAsync(requestParams, atsParams).ConfigureAwait(false); // Assert Assert.AreSame(_msalTokenResponse, result); Assert.AreEqual("yes", webTokenRequest.Properties["validateAuthority"]); Assert.AreEqual("extraVal1", webTokenRequest.Properties["extraQp1"]); // Although at the time of writing, MSAL does not support instance aware ... // WAM does support it but the param is different - discovery=home Assert.AreEqual("home", webTokenRequest.Properties["discover"]); Assert.AreEqual("https://login.microsoftonline.com/organizations/", webTokenRequest.Properties["authority"]); } }
private async Task <MsalTokenResponse> AcquireInteractiveWithPickerAsync( AuthenticationRequestParameters authenticationRequestParameters, Prompt msalPrompt) { bool isMsaPassthrough = authenticationRequestParameters.AppConfig.IsMsaPassthrough; var accountPicker = _accountPickerFactory.Create( _parentHandle, _logger, _synchronizationContext, authenticationRequestParameters.Authority, isMsaPassthrough); IWamPlugin wamPlugin; WebTokenRequest webTokenRequest; try { WebAccountProvider accountProvider = await accountPicker.DetermineAccountInteractivelyAsync().ConfigureAwait(false); if (accountProvider == null) { throw new MsalClientException(MsalError.AuthenticationCanceledError, "WAM Account Picker did not return an account."); } bool isConsumerTenant = _webAccountProviderFactory.IsConsumerProvider(accountProvider); // WAM returns the tenant here, not the full authority wamPlugin = (isConsumerTenant && !isMsaPassthrough) ? _msaPlugin : _aadPlugin; string transferToken = null; bool isForceLoginPrompt = false; if (isConsumerTenant && isMsaPassthrough) { // Get a transfer token to avoid prompting the user twice transferToken = await _msaPassthroughHandler.TryFetchTransferTokenAsync( authenticationRequestParameters, accountProvider).ConfigureAwait(false); // If a TT cannot be obtained, force the interactive experience again isForceLoginPrompt = string.IsNullOrEmpty(transferToken); // For MSA-PT, the MSA provider will issue v1 token, which cannot be used. // Only the AAD provider can issue a v2 token accountProvider = await _webAccountProviderFactory.GetAccountProviderAsync( authenticationRequestParameters.Authority.TenantId) .ConfigureAwait(false); } webTokenRequest = await wamPlugin.CreateWebTokenRequestAsync( accountProvider, authenticationRequestParameters, isForceLoginPrompt : isForceLoginPrompt, isInteractive : true, isAccountInWam : false) .ConfigureAwait(true); _msaPassthroughHandler.AddTransferTokenToRequest(webTokenRequest, transferToken); WamAdapters.AddMsalParamsToRequest(authenticationRequestParameters, webTokenRequest); AddPromptToRequest(msalPrompt, isForceLoginPrompt, webTokenRequest); } catch (Exception ex) when(!(ex is MsalException)) { _logger.ErrorPii(ex); throw new MsalServiceException( MsalError.WamPickerError, "Could not get the account provider - account picker. See inner exception for details", ex); } IWebTokenRequestResultWrapper wamResult; try { wamResult = await _wamProxy.RequestTokenForWindowAsync(_parentHandle, webTokenRequest).ConfigureAwait(false); } catch (Exception ex) { _logger.ErrorPii(ex); throw new MsalServiceException( MsalError.WamPickerError, "Could not get the result - account picker. See inner exception for details", ex); } return(CreateMsalTokenResponse(wamResult, wamPlugin, isInteractive: true)); }