public void Init() { _synchronizationContext = new DedicatedThreadSynchronisationContext(); _coreUIParent = new CoreUIParent() { SynchronizationContext = _synchronizationContext }; _logger = Substitute.For <ICoreLogger>(); _aadPlugin = Substitute.For <IWamPlugin>(); _msaPlugin = Substitute.For <IWamPlugin>(); _wamProxy = Substitute.For <IWamProxy>(); _webAccountProviderFactory = Substitute.For <IWebAccountProviderFactory>(); _accountPickerFactory = Substitute.For <IAccountPickerFactory>(); _wamBroker = new WamBroker( _coreUIParent, _logger, _aadPlugin, _msaPlugin, _wamProxy, _webAccountProviderFactory, _accountPickerFactory); }
public async Task <MsalTokenResponse> AcquireTokenSilentDefaultUserAsync( AuthenticationRequestParameters authenticationRequestParameters, AcquireTokenSilentParameters acquireTokenSilentParameters) { using (_logger.LogMethodDuration()) { bool isMsa = await IsMsaRequestAsync( authenticationRequestParameters.Authority, null, IsMsaPassthrough(authenticationRequestParameters)).ConfigureAwait(false); IWamPlugin wamPlugin = isMsa ? _msaPlugin : _aadPlugin; WebAccountProvider provider = await GetProviderAsync( authenticationRequestParameters.Authority.AuthorityInfo.CanonicalAuthority, isMsa).ConfigureAwait(false); WebTokenRequest webTokenRequest = await wamPlugin.CreateWebTokenRequestAsync( provider, authenticationRequestParameters, isForceLoginPrompt : false, isAccountInWam : false, isInteractive : false) .ConfigureAwait(false); AddCommonParamsToRequest(authenticationRequestParameters, webTokenRequest); var wamResult = await _wamProxy.GetTokenSilentlyForDefaultAccountAsync(webTokenRequest).ConfigureAwait(false); return(CreateMsalTokenResponse(wamResult, wamPlugin, isInteractive: false)); } }
private static WebAccount MatchWamAccountToMsalAccount( IWamPlugin wamPlugin, IAccount account, string loginHint, IEnumerable <WebAccount> wamAccounts) { WebAccount matchedAccountByLoginHint = null; foreach (var wamAccount in wamAccounts) { string homeAccountId = wamPlugin.GetHomeAccountIdOrNull(wamAccount); if (!string.IsNullOrEmpty(homeAccountId) && string.Equals(homeAccountId, account?.HomeAccountId?.Identifier, StringComparison.OrdinalIgnoreCase)) { return(wamAccount); } if (!string.IsNullOrEmpty(loginHint) && string.Equals(loginHint, wamAccount.UserName, StringComparison.OrdinalIgnoreCase)) { matchedAccountByLoginHint = wamAccount; } } return(matchedAccountByLoginHint); }
/// <summary> /// Ctor. Only call if on Win10, otherwise a TypeLoadException occurs. See DesktopOsHelper.IsWin10 /// </summary> public WamBroker( CoreUIParent uiParent, ApplicationConfiguration appConfig, ICoreLogger logger, IWamPlugin testAadPlugin = null, IWamPlugin testmsaPlugin = null, IWamProxy wamProxy = null, IWebAccountProviderFactory webAccountProviderFactory = null, IAccountPickerFactory accountPickerFactory = null, IMsaPassthroughHandler msaPassthroughHandler = null) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _synchronizationContext = uiParent?.SynchronizationContext; _wamProxy = wamProxy ?? new WamProxy(_logger, _synchronizationContext); _parentHandle = GetParentWindow(uiParent); _webAccountProviderFactory = webAccountProviderFactory ?? new WebAccountProviderFactory(); _accountPickerFactory = accountPickerFactory ?? new AccountPickerFactory(); _aadPlugin = testAadPlugin ?? new AadPlugin(_wamProxy, _webAccountProviderFactory, _logger); _msaPlugin = testmsaPlugin ?? new MsaPlugin(_wamProxy, _webAccountProviderFactory, _logger); _msaPassthroughHandler = msaPassthroughHandler ?? new MsaPassthroughHandler(_logger, _msaPlugin, _wamProxy, _parentHandle); _wamOptions = appConfig.WindowsBrokerOptions ?? WindowsBrokerOptions.CreateDefault(); }
public async Task RemoveAccountAsync(ApplicationConfiguration appConfig, IAccount account) { string homeTenantId = account?.HomeAccountId?.TenantId; if (!string.IsNullOrEmpty(homeTenantId)) { bool isMsaAccount = IsConsumerTenantId(homeTenantId); IWamPlugin wamPlugin = isMsaAccount ? _msaPlugin : _aadPlugin; WebAccountProvider provider; if (isMsaAccount) { provider = await _webAccountProviderFactory.GetAccountProviderAsync("consumers").ConfigureAwait(false); } else { provider = await _webAccountProviderFactory.GetAccountProviderAsync("organizations") .ConfigureAwait(false); } var webAccount = await FindWamAccountForMsalAccountAsync(provider, wamPlugin, account, null, appConfig.ClientId) .ConfigureAwait(false); _logger.Info("Found a webAccount? " + (webAccount != null)); if (webAccount != null) { await webAccount.SignOutAsync(); } } }
/// <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)); }
private MsalTokenResponse CreateMsalTokenResponse( IWebTokenRequestResultWrapper wamResponse, IWamPlugin wamPlugin, bool isInteractive) { string internalErrorCode = null; string errorMessage; string errorCode; switch (wamResponse.ResponseStatus) { case WebTokenRequestStatus.Success: _logger.Info("WAM response status success"); return(wamPlugin.ParseSuccesfullWamResponse(wamResponse.ResponseData[0])); // Account Switch occurs when a login hint is passed to WAM but the user chooses a different account for login. // MSAL treats this as a success scenario case WebTokenRequestStatus.AccountSwitch: _logger.Info("WAM response status account switch. Treating as success"); return(wamPlugin.ParseSuccesfullWamResponse(wamResponse.ResponseData[0])); case WebTokenRequestStatus.UserInteractionRequired: errorCode = wamPlugin.MapTokenRequestError(wamResponse.ResponseStatus, wamResponse.ResponseError?.ErrorCode ?? 0, isInteractive); internalErrorCode = (wamResponse.ResponseError?.ErrorCode ?? 0).ToString(CultureInfo.InvariantCulture); errorMessage = WamErrorPrefix + $"Wam plugin {wamPlugin.GetType()}" + $" error code: {internalErrorCode}" + $" error: " + wamResponse.ResponseError?.ErrorMessage; break; case WebTokenRequestStatus.UserCancel: errorCode = MsalError.AuthenticationCanceledError; errorMessage = MsalErrorMessage.AuthenticationCanceled; break; case WebTokenRequestStatus.ProviderError: errorCode = wamPlugin.MapTokenRequestError(wamResponse.ResponseStatus, wamResponse.ResponseError?.ErrorCode ?? 0, isInteractive); errorMessage = WamErrorPrefix + wamPlugin.GetType() + wamResponse.ResponseError?.ErrorMessage; internalErrorCode = (wamResponse.ResponseError?.ErrorCode ?? 0).ToString(CultureInfo.InvariantCulture); break; default: errorCode = MsalError.UnknownBrokerError; internalErrorCode = wamResponse.ResponseError.ErrorCode.ToString(CultureInfo.InvariantCulture); errorMessage = $"Unknown WebTokenRequestStatus {wamResponse.ResponseStatus} (internal error code {internalErrorCode})"; break; } return(new MsalTokenResponse() { Error = errorCode, ErrorCodes = internalErrorCode != null ? new[] { internalErrorCode } : null, ErrorDescription = errorMessage }); }
private async Task <IWebTokenRequestResultWrapper> AcquireInteractiveWithoutPickerAsync( AuthenticationRequestParameters authenticationRequestParameters, Prompt prompt, IWamPlugin wamPlugin, WebAccountProvider provider, WebAccount wamAccount) { bool isForceLoginPrompt = IsForceLoginPrompt(prompt); WebTokenRequest webTokenRequest = await wamPlugin.CreateWebTokenRequestAsync( provider, authenticationRequestParameters, isForceLoginPrompt : isForceLoginPrompt, isInteractive : true, isAccountInWam : true) .ConfigureAwait(false); if (isForceLoginPrompt && ApiInformation.IsApiContractPresent("Windows.Foundation.UniversalApiContract", 6)) { // this feature works correctly since windows RS4, aka 1803 with the AAD plugin only! webTokenRequest.Properties["prompt"] = prompt.PromptValue; } AddCommonParamsToRequest(authenticationRequestParameters, webTokenRequest); try { #if WINDOWS_APP // UWP requires being on the UI thread await _synchronizationContext; #endif IWebTokenRequestResultWrapper wamResult; if (wamAccount != null) { wamResult = await _wamProxy.RequestTokenForWindowAsync( _parentHandle, webTokenRequest, wamAccount).ConfigureAwait(false); } else { // default user wamResult = await _wamProxy.RequestTokenForWindowAsync( _parentHandle, webTokenRequest).ConfigureAwait(false); } return(wamResult); } catch (Exception ex) { _logger.ErrorPii(ex); throw new MsalServiceException( MsalError.WamInteractiveError, "AcquireTokenInteractive without picker failed. See inner exception for details. ", ex); } }
public async Task <MsalTokenResponse> AcquireTokenSilentAsync( AuthenticationRequestParameters authenticationRequestParameters, AcquireTokenSilentParameters acquireTokenSilentParameters) { using (_logger.LogMethodDuration()) { // Important: MSAL will have already resolved the authority by now, // so we are not expecting "common" or "organizations" but a tenanted authority bool isMsa = IsMsaRequest( authenticationRequestParameters.Authority, null, 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); } WebAccount webAccount = await FindWamAccountForMsalAccountAsync( provider, wamPlugin, authenticationRequestParameters.Account, null, // ATS requires an account object, login_hint is not supported on its own authenticationRequestParameters.ClientId).ConfigureAwait(false); if (webAccount == null) { throw new MsalUiRequiredException( MsalError.InteractionRequired, "Could not find a WAM account for the silent request."); } WebTokenRequest webTokenRequest = await wamPlugin.CreateWebTokenRequestAsync( provider, authenticationRequestParameters, isForceLoginPrompt : false, isAccountInWam : true, isInteractive : false) .ConfigureAwait(false); AddCommonParamsToRequest(authenticationRequestParameters, webTokenRequest); var wamResult = await _wamProxy.GetTokenSilentlyAsync(webAccount, webTokenRequest).ConfigureAwait(false); return(CreateMsalTokenResponse(wamResult, wamPlugin, isInteractive: false)); } }
public MsaPassthroughHandler( ICoreLogger logger, IWamPlugin msaPlugin, IWamProxy wamProxy, IntPtr parentHandle) { _logger = logger; _msaPlugin = msaPlugin; _wamProxy = wamProxy; _parentHandle = parentHandle; }
public void Init() { _logger = Substitute.For <ICoreLogger>(); _msaPlugin = Substitute.For <IWamPlugin>(); _wamProxy = Substitute.For <IWamProxy>(); _msaPassthroughHandler = new MsaPassthroughHandler( _logger, _msaPlugin, _wamProxy, IntPtr.Zero); }
private async Task <IWebTokenRequestResultWrapper> AcquireInteractiveWithWamAccountAsync( AuthenticationRequestParameters authenticationRequestParameters, Prompt msalPrompt, IWamPlugin wamPlugin, WebAccountProvider provider, WebAccount wamAccount) { WebTokenRequest webTokenRequest = await wamPlugin.CreateWebTokenRequestAsync( provider, authenticationRequestParameters, isForceLoginPrompt : false, isInteractive : true, isAccountInWam : true) .ConfigureAwait(false); // because of https://github.com/AzureAD/microsoft-authentication-library-for-dotnet/issues/2476 string differentAuthority = null; if (string.Equals(wamAccount?.WebAccountProvider?.Authority, Constants.OrganizationsTenant) && string.Equals(authenticationRequestParameters.Authority.TenantId, Constants.OrganizationsTenant)) { differentAuthority = authenticationRequestParameters.Authority.GetTenantedAuthority("common"); } WamAdapters.AddMsalParamsToRequest(authenticationRequestParameters, webTokenRequest, differentAuthority); try { IWebTokenRequestResultWrapper wamResult; if (wamAccount != null) { wamResult = await _wamProxy.RequestTokenForWindowAsync( _parentHandle, webTokenRequest, wamAccount).ConfigureAwait(false); } else { // default user wamResult = await _wamProxy.RequestTokenForWindowAsync( _parentHandle, webTokenRequest).ConfigureAwait(false); } return(wamResult); } catch (Exception ex) { _logger.ErrorPii(ex); throw new MsalServiceException( MsalError.WamInteractiveError, "AcquireTokenInteractive without picker failed. See inner exception for details. ", ex); } }
public async Task <MsalTokenResponse> AcquireTokenSilentDefaultUserAsync( AuthenticationRequestParameters authenticationRequestParameters, AcquireTokenSilentParameters acquireTokenSilentParameters) { using (_logger.LogMethodDuration()) { var defaultAccountProvider = await _webAccountProviderFactory.GetDefaultProviderAsync().ConfigureAwait(false); if (defaultAccountProvider == null) { throw new MsalUiRequiredException( MsalError.InteractionRequired, "A default account was not found"); } // special case: passthrough + default MSA account. Need to use the transfer token protocol. if (_wamOptions.MsaPassthrough && _webAccountProviderFactory.IsConsumerProvider(defaultAccountProvider)) { return(await AcquireTokenSilentDefaultUserPassthroughAsync(authenticationRequestParameters, defaultAccountProvider).ConfigureAwait(false)); } bool isMsa = await IsMsaRequestAsync( authenticationRequestParameters.Authority, null, _wamOptions.MsaPassthrough).ConfigureAwait(false); IWamPlugin wamPlugin = isMsa ? _msaPlugin : _aadPlugin; WebAccountProvider provider = await GetProviderAsync( authenticationRequestParameters.Authority.AuthorityInfo.CanonicalAuthority, isMsa).ConfigureAwait(false); WebTokenRequest webTokenRequest = await wamPlugin.CreateWebTokenRequestAsync( provider, authenticationRequestParameters, isForceLoginPrompt : false, isAccountInWam : false, isInteractive : false) .ConfigureAwait(false); WamAdapters.AddMsalParamsToRequest(authenticationRequestParameters, webTokenRequest, _logger); var wamResult = await _wamProxy.GetTokenSilentlyForDefaultAccountAsync(webTokenRequest).ConfigureAwait(false); return(WamAdapters.CreateMsalResponseFromWamResponse( wamResult, wamPlugin, authenticationRequestParameters.AppConfig.ClientId, _logger, isInteractive: false)); } }
public void Init() { _logger = Substitute.For <ICoreLogger>(); _wamProxy = Substitute.For <IWamProxy>(); _webAccountProviderFactory = Substitute.For <IWebAccountProviderFactory>(); _accountPickerFactory = Substitute.For <IAccountPickerFactory>(); _webAccountProviderFactory.ClearReceivedCalls(); _cacheSessionManager = Substitute.For <ICacheSessionManager>(); _instanceDiscoveryManager = Substitute.For <IInstanceDiscoveryManager>(); _aadPlugin = new AadPlugin(_wamProxy, _webAccountProviderFactory, _logger); }
private async Task <IWebTokenRequestResultWrapper> AcquireInteractiveWithoutPickerAsync( AuthenticationRequestParameters authenticationRequestParameters, Prompt msalPrompt, IWamPlugin wamPlugin, WebAccountProvider provider, WebAccount wamAccount) { bool isForceLoginPrompt = IsForceLoginPrompt(msalPrompt); WebTokenRequest webTokenRequest = await wamPlugin.CreateWebTokenRequestAsync( provider, authenticationRequestParameters, isForceLoginPrompt : isForceLoginPrompt, isInteractive : true, isAccountInWam : true) .ConfigureAwait(false); AddPromptToRequest(msalPrompt, isForceLoginPrompt, webTokenRequest); WamAdapters.AddMsalParamsToRequest(authenticationRequestParameters, webTokenRequest); try { IWebTokenRequestResultWrapper wamResult; if (wamAccount != null) { wamResult = await _wamProxy.RequestTokenForWindowAsync( _parentHandle, webTokenRequest, wamAccount).ConfigureAwait(false); } else { // default user wamResult = await _wamProxy.RequestTokenForWindowAsync( _parentHandle, webTokenRequest).ConfigureAwait(false); } return(wamResult); } catch (Exception ex) { _logger.ErrorPii(ex); throw new MsalServiceException( MsalError.WamInteractiveError, "AcquireTokenInteractive without picker failed. See inner exception for details. ", ex); } }
public WamBroker( CoreUIParent uiParent, ICoreLogger logger, IWamPlugin testAadPlugin = null, IWamPlugin testmsaPlugin = null, IWamProxy wamProxy = null, IWebAccountProviderFactory webAccountProviderFactory = null, IAccountPickerFactory accountPickerFactory = null) { _logger = logger ?? throw new ArgumentNullException(nameof(logger)); _wamProxy = wamProxy ?? new WamProxy(_logger); _parentHandle = GetParentWindow(uiParent); _synchronizationContext = uiParent?.SynchronizationContext; _webAccountProviderFactory = webAccountProviderFactory ?? new WebAccountProviderFactory(); _accountPickerFactory = accountPickerFactory ?? new AccountPickerFactory(); _aadPlugin = testAadPlugin ?? new AadPlugin(_wamProxy, _webAccountProviderFactory, _logger); _msaPlugin = testmsaPlugin ?? new MsaPlugin(_wamProxy, _webAccountProviderFactory, _logger); }
public async Task RemoveAccountAsync(ApplicationConfiguration appConfig, IAccount account) { string homeTenantId = account?.HomeAccountId?.TenantId; if (!string.IsNullOrEmpty(homeTenantId)) { // If it's an AAD account, only the AAD plugin should remove it // If it's an MSA account - MSA plugin should remove it, but in MSA-PT scenarios it's still the AAD plugin bool isMsaRequest = await IsMsaRequestAsync( appConfig.Authority, homeTenantId, _wamOptions.MsaPassthrough).ConfigureAwait(false); IWamPlugin wamPlugin = isMsaRequest ? _msaPlugin : _aadPlugin; WebAccountProvider provider; if (isMsaRequest) { provider = await _webAccountProviderFactory.GetAccountProviderAsync("consumers").ConfigureAwait(false); } else { provider = await _webAccountProviderFactory.GetAccountProviderAsync("organizations") .ConfigureAwait(false); } var webAccount = await FindWamAccountForMsalAccountAsync(provider, wamPlugin, account, null, appConfig.ClientId) .ConfigureAwait(false); _logger.Info("Found a webAccount? " + (webAccount != null)); if (webAccount != null) { await webAccount.SignOutAsync(); } } }
private async Task <WebAccount> FindWamAccountForMsalAccountAsync( WebAccountProvider provider, IWamPlugin wamPlugin, IAccount msalAccount, string loginHint, string clientId) { if (msalAccount == null && string.IsNullOrEmpty(loginHint)) { return(null); } Account accountInternal = (msalAccount as Account); if (accountInternal?.WamAccountIds != null && accountInternal.WamAccountIds.TryGetValue(clientId, out string wamAccountId)) { _logger.Info("WAM will try to find an account based on the WAM account id from the cache"); WebAccount result = await _wamProxy.FindAccountAsync(provider, wamAccountId).ConfigureAwait(false); if (result != null) { return(result); } _logger.Warning("WAM account was not found for given WAM account id."); } var wamAccounts = await _wamProxy.FindAllWebAccountsAsync(provider, clientId).ConfigureAwait(false); return(MatchWamAccountToMsalAccount( wamPlugin, msalAccount, loginHint, wamAccounts)); }
/// <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 WINDOWS_APP if (_synchronizationContext == null) { throw new MsalClientException( MsalError.WamUiThread, "AcquireTokenInteractive with broker must be called from the UI thread when using WAM." + ErrorMessageSuffix); } #endif if (authenticationRequestParameters.Account != null || !string.IsNullOrEmpty(authenticationRequestParameters.LoginHint)) { _logger.Verbose("[WamBroker] AcquireTokenIntractive - account information provided. Trying to find a Windows account that matches."); bool isMsaPassthrough = _wamOptions.MsaPassthrough; bool isMsa = await IsMsaRequestAsync( authenticationRequestParameters.Authority, authenticationRequestParameters?.Account?.HomeAccountId?.TenantId, isMsaPassthrough).ConfigureAwait(false); IWamPlugin wamPlugin = isMsa ? _msaPlugin : _aadPlugin; WebAccountProvider provider = await GetProviderAsync( authenticationRequestParameters.Authority.TenantId, isMsa) .ConfigureAwait(false); if (PublicClientApplication.IsOperatingSystemAccount(authenticationRequestParameters.Account)) { var wamResult = await AcquireInteractiveWithWamAccountAsync( authenticationRequestParameters, acquireTokenInteractiveParameters.Prompt, wamPlugin, provider, null) .ConfigureAwait(false); return(WamAdapters.CreateMsalResponseFromWamResponse( wamResult, wamPlugin, authenticationRequestParameters.AppConfig.ClientId, _logger, isInteractive: true)); } var wamAccount = await FindWamAccountForMsalAccountAsync( provider, wamPlugin, authenticationRequestParameters.Account, authenticationRequestParameters.LoginHint, authenticationRequestParameters.AppConfig.ClientId).ConfigureAwait(false); if (wamAccount != null) { var wamResult = await AcquireInteractiveWithWamAccountAsync( authenticationRequestParameters, acquireTokenInteractiveParameters.Prompt, wamPlugin, provider, wamAccount) .ConfigureAwait(false); return(WamAdapters.CreateMsalResponseFromWamResponse( wamResult, wamPlugin, authenticationRequestParameters.AppConfig.ClientId, _logger, isInteractive: true)); } _logger.Verbose("[WamBroker] AcquireTokenIntractive - account information provided but no matching account was found "); } // no account information available, need an account picker if (CanSkipAccountPicker(authenticationRequestParameters.Authority)) { _logger.Verbose("[WamBroker] Using AAD plugin account picker"); return(await AcquireInteractiveWithAadBrowserAsync( authenticationRequestParameters, acquireTokenInteractiveParameters.Prompt).ConfigureAwait(false)); } _logger.Verbose("[WamBroker] Using Windows account picker (AccountsSettingsPane)"); return(await AcquireInteractiveWithPickerAsync( authenticationRequestParameters, acquireTokenInteractiveParameters.Prompt) .ConfigureAwait(false)); }
/// <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 WINDOWS_APP if (_synchronizationContext == null) { throw new MsalClientException( MsalError.WamUiThread, "AcquireTokenInteractive with broker must be called from the UI thread when using WAM." + ErrorMessageSuffix); } #endif if (authenticationRequestParameters.Account != null || !string.IsNullOrEmpty(authenticationRequestParameters.LoginHint)) { bool isMsaPassthrough = _wamOptions.MsaPassthrough; bool isMsa = await IsMsaRequestAsync( authenticationRequestParameters.Authority, authenticationRequestParameters?.Account?.HomeAccountId?.TenantId, // TODO: we could further optimize here by searching for an account based on UPN isMsaPassthrough).ConfigureAwait(false); IWamPlugin wamPlugin = isMsa ? _msaPlugin : _aadPlugin; WebAccountProvider provider = await GetProviderAsync( authenticationRequestParameters.Authority.TenantId, isMsa) .ConfigureAwait(false); if (PublicClientApplication.IsOperatingSystemAccount(authenticationRequestParameters.Account)) { var wamResult = await AcquireInteractiveWithWamAccountAsync( authenticationRequestParameters, acquireTokenInteractiveParameters.Prompt, wamPlugin, provider, null) .ConfigureAwait(false); return(WamAdapters.CreateMsalResponseFromWamResponse( wamResult, wamPlugin, authenticationRequestParameters.AppConfig.ClientId, _logger, isInteractive: true)); } var wamAccount = await FindWamAccountForMsalAccountAsync( provider, wamPlugin, authenticationRequestParameters.Account, authenticationRequestParameters.LoginHint, authenticationRequestParameters.AppConfig.ClientId).ConfigureAwait(false); if (wamAccount != null) { var wamResult = await AcquireInteractiveWithWamAccountAsync( authenticationRequestParameters, acquireTokenInteractiveParameters.Prompt, wamPlugin, provider, wamAccount) .ConfigureAwait(false); return(WamAdapters.CreateMsalResponseFromWamResponse( wamResult, wamPlugin, authenticationRequestParameters.AppConfig.ClientId, _logger, isInteractive: true)); } else { if (IsAadOnlyAuthority(authenticationRequestParameters.Authority)) { return(await AcquireInteractiveWithAadBrowserAsync( authenticationRequestParameters, acquireTokenInteractiveParameters.Prompt).ConfigureAwait(false)); } } } return(await AcquireInteractiveWithPickerAsync( authenticationRequestParameters, acquireTokenInteractiveParameters.Prompt) .ConfigureAwait(false)); }
internal static MsalTokenResponse CreateMsalResponseFromWamResponse( IWebTokenRequestResultWrapper wamResponse, IWamPlugin wamPlugin, string clientId, ICoreLogger logger, bool isInteractive) { string internalErrorCode = null; string errorMessage; string errorCode; switch (wamResponse.ResponseStatus) { case WebTokenRequestStatus.Success: logger.Info("WAM response status success"); return(wamPlugin.ParseSuccessfullWamResponse(wamResponse.ResponseData[0], out _)); // Account Switch occurs when a login hint is passed to WAM but the user chooses a different account for login. // MSAL treats this as a success scenario case WebTokenRequestStatus.AccountSwitch: logger.Info("WAM response status account switch. Treating as success"); return(wamPlugin.ParseSuccessfullWamResponse(wamResponse.ResponseData[0], out _)); case WebTokenRequestStatus.UserInteractionRequired: errorCode = wamPlugin.MapTokenRequestError(wamResponse.ResponseStatus, wamResponse.ResponseError?.ErrorCode ?? 0, isInteractive); internalErrorCode = (wamResponse.ResponseError?.ErrorCode ?? 0).ToString(CultureInfo.InvariantCulture); errorMessage = WamErrorPrefix + $"Wam plugin {wamPlugin.GetType()}" + $" Error code: {internalErrorCode}" + $" Error Message: " + wamResponse.ResponseError?.ErrorMessage; break; case WebTokenRequestStatus.UserCancel: errorCode = MsalError.AuthenticationCanceledError; errorMessage = MsalErrorMessage.AuthenticationCanceled; break; case WebTokenRequestStatus.ProviderError: errorCode = wamPlugin.MapTokenRequestError(wamResponse.ResponseStatus, wamResponse.ResponseError?.ErrorCode ?? 0, isInteractive); errorMessage = $"{WamErrorPrefix} {wamPlugin.GetType()} \n" + $" Error Code: {errorCode} \n" + $" Error Message: {wamResponse.ResponseError?.ErrorMessage} \n" + $" Possible causes: \n " + $"- Invalid redirect uri - ensure you have configured the following url in the AAD portal App Registration: {GetExpectedRedirectUri(clientId)} \n" + $"- No Internet connection \n" + $"Please see https://aka.ms/msal-net-wam for details about Windows Broker integration"; internalErrorCode = (wamResponse.ResponseError?.ErrorCode ?? 0).ToString(CultureInfo.InvariantCulture); break; default: errorCode = MsalError.UnknownBrokerError; internalErrorCode = wamResponse.ResponseError.ErrorCode.ToString(CultureInfo.InvariantCulture); errorMessage = $"Unknown WebTokenRequestStatus {wamResponse.ResponseStatus} (internal error code {internalErrorCode})"; break; } return(new MsalTokenResponse() { Error = errorCode, ErrorCodes = internalErrorCode != null ? new[] { internalErrorCode } : null, ErrorDescription = errorMessage }); }
public async Task <MsalTokenResponse> AcquireTokenSilentAsync( AuthenticationRequestParameters authenticationRequestParameters, AcquireTokenSilentParameters acquireTokenSilentParameters) { using (_logger.LogMethodDuration()) { // Important: MSAL will have already resolved the authority by now, // so we are not expecting "common" or "organizations" but a tenanted authority bool isMsa = await IsMsaRequestAsync( authenticationRequestParameters.Authority, null, _wamOptions.MsaPassthrough) .ConfigureAwait(false); IWamPlugin wamPlugin = isMsa ? _msaPlugin : _aadPlugin; WebAccountProvider provider; if (_wamOptions.MsaPassthrough) { provider = await GetProviderAsync( "organizations", false).ConfigureAwait(false); } else { provider = await GetProviderAsync( authenticationRequestParameters.AuthorityInfo.CanonicalAuthority, isMsa).ConfigureAwait(false); } WebAccount webAccount = await FindWamAccountForMsalAccountAsync( provider, wamPlugin, authenticationRequestParameters.Account, null, // ATS requires an account object, login_hint is not supported on its own authenticationRequestParameters.AppConfig.ClientId).ConfigureAwait(false); if (webAccount == null && _wamOptions.MsaPassthrough) { return(await AcquireMsaTokenSilentForPassthroughAsync( authenticationRequestParameters, provider).ConfigureAwait(false)); } if (webAccount == null) { throw new MsalUiRequiredException( MsalError.InteractionRequired, "Could not find a WAM account for the silent request."); } WebTokenRequest webTokenRequest = await wamPlugin.CreateWebTokenRequestAsync( provider, authenticationRequestParameters, isForceLoginPrompt : false, isAccountInWam : true, isInteractive : false) .ConfigureAwait(false); // For MSA-PT scenario, MSAL's authority is wrong. MSAL will use Account.HomeTenantId // which will essentialyl be /consumers. This is wrong, we are not trying to obtain // an MSA token, we are trying to obtain an ADD *guest* token. string differentAuthority = null; if (_wamOptions.MsaPassthrough && authenticationRequestParameters.Authority is AadAuthority aadAuthority && aadAuthority.IsConsumers()) { differentAuthority = authenticationRequestParameters.Authority.GetTenantedAuthority("organizations", forceTenantless: true); } WamAdapters.AddMsalParamsToRequest(authenticationRequestParameters, webTokenRequest, _logger, differentAuthority); var wamResult = await _wamProxy.GetTokenSilentlyAsync(webAccount, webTokenRequest).ConfigureAwait(false); return(WamAdapters.CreateMsalResponseFromWamResponse( wamResult, wamPlugin, authenticationRequestParameters.AppConfig.ClientId, _logger, isInteractive: false)); } }