From 0adea97ea01da9c86519d413b90e55a19883683e Mon Sep 17 00:00:00 2001 From: Dharshan BJ Date: Tue, 22 Jul 2025 13:31:51 -0700 Subject: [PATCH 01/42] update --- Directory.Packages.props | 6 +- .../Microsoft.Identity.Client.Desktop.csproj | 41 +- .../WebView2WebUi/WebView2WebUi.cs | 385 ++++++++++++++++- .../WebView2WebUi/WebView2WebUiFactory.cs | 7 +- .../WinUI3WindowWithWebView2.xaml | 24 ++ .../WinUI3WindowWithWebView2.xaml.cs | 395 ++++++++++++++++++ .../Interactive/AuthCodeRequestComponent.cs | 3 +- 7 files changed, 829 insertions(+), 32 deletions(-) create mode 100644 src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml create mode 100644 src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml.cs diff --git a/Directory.Packages.props b/Directory.Packages.props index f222636fe1..3269cc6e79 100644 --- a/Directory.Packages.props +++ b/Directory.Packages.props @@ -4,7 +4,7 @@ 0.19.2 - 4.61.0 + 4.76.0 @@ -13,7 +13,9 @@ - + + + diff --git a/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj b/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj index 5856d69bfd..b5045a6b99 100644 --- a/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj +++ b/src/client/Microsoft.Identity.Client.Desktop/Microsoft.Identity.Client.Desktop.csproj @@ -1,12 +1,12 @@ - + net462 netcoreapp3.1 - $(TargetFrameworkNetDesktop);$(TargetFrameworkNetCore) + net8.0-windows10.0.17763.0 + $(TargetFrameworkNetDesktop);$(TargetFrameworkNetCore);$(TargetFrameworkModernTFM) $(MSBuildThisFileDirectory)../Microsoft.Identity.Client/ - true AnyCPU Debug;Release;Debug + MobileApps @@ -30,10 +30,37 @@ true - + + true + + + + true + + + $(DefineConstants);NET_CORE + + $(DefineConstants);WINRT + + + + + + + + + + + + + + + + + @@ -47,8 +74,12 @@ - + + + + + diff --git a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs index f850ec5236..d6dde793a6 100644 --- a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs +++ b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs @@ -11,6 +11,15 @@ using Microsoft.Identity.Client.Platforms.Features.DesktopOs; using Microsoft.Identity.Client.UI; using Microsoft.Identity.Client.Core; +#if WINRT +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI.Windowing; +using Windows.Graphics; +using Windows.Graphics.Display; +using Microsoft.UI; +using Microsoft.UI.Dispatching; +#endif namespace Microsoft.Identity.Client.Desktop.WebView2WebUi { @@ -25,42 +34,356 @@ public WebView2WebUi(CoreUIParent parent, RequestContext requestContext) _requestContext = requestContext; } - public async Task AcquireAuthorizationAsync( - Uri authorizationUri, - Uri redirectUri, - RequestContext requestContext, - CancellationToken cancellationToken) + // public async Task AcquireAuthorizationAsync( + // Uri authorizationUri, + // Uri redirectUri, + // RequestContext requestContext, + // CancellationToken cancellationToken) + // { + // AuthorizationResult result = null; + + // #if WINRT + // var sendAuthorizeRequest = new Func(async () => + // { + // result = await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); + // }); + // #else + // var sendAuthorizeRequest = new Action(() => + // { + // result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + // }); + // #endif + + // if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) + // { + // if (_parent.SynchronizationContext != null) + // { + // #if WINRT + // var sendAuthorizeRequestWithTcs = new Func(async (tcs) => + // { + // try + // { + // result = await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); + // #else + // var sendAuthorizeRequestWithTcs = new Action((tcs) => + // { + // try + // { + // result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + // #endif + // ((TaskCompletionSource)tcs).TrySetResult(null); + // } + // catch (Exception e) + // { + // // Need to catch the exception here and put on the TCS which is the task we are waiting on so that + // // the exception coming out of Authenticate is correctly thrown. + // ((TaskCompletionSource)tcs).TrySetException(e); + // } + // }); + + // var tcs2 = new TaskCompletionSource(); + + // _parent.SynchronizationContext.Post( + // #if WINRT + // new SendOrPostCallback((state) => + // { + // Task.Run(() => sendAuthorizeRequestWithTcs(state)); + // }), tcs2); + // #else + // new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); + // await tcs2.Task.ConfigureAwait(false); + // #endif + // } + // else + // { + // using (var staTaskScheduler = new StaTaskScheduler(1)) + // { + // try + // { + // Task.Factory.StartNew( + // sendAuthorizeRequest, + // cancellationToken, + // TaskCreationOptions.None, + // staTaskScheduler).Wait(cancellationToken); + // } + // catch (AggregateException ae) + // { + // requestContext.Logger.ErrorPii(ae.InnerException); + // // Any exception thrown as a result of running task will cause AggregateException to be thrown with + // // actual exception as inner. + // Exception innerException = ae.InnerExceptions[0]; + + // // In MTA case, AggregateException is two layer deep, so checking the InnerException for that. + // if (innerException is AggregateException exception) + // { + // innerException = exception.InnerExceptions[0]; + // } + + // throw innerException; + // } + // } + // } + // } + // else + // { + // #if WINRT + // await sendAuthorizeRequest().ConfigureAwait(false); + // #else + // sendAuthorizeRequest(); + // #endif + // } + + // return result; + + // } + + // public async Task AcquireAuthorizationAsync( + // Uri authorizationUri, + // Uri redirectUri, + // RequestContext requestContext, + // CancellationToken cancellationToken) + // { + // AuthorizationResult result = null; + + // #if WINRT + // var sendAuthorizeRequest = new Func(async () => + // { + // result = await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); + // }); + // #else + // var sendAuthorizeRequest = new Action(() => + // { + // result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + // }); + // #endif + + // if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) + // { + // if (_parent.SynchronizationContext != null) + // { + // #if WINRT + // // For WINRT/WinUI3, use ContinueWith pattern for async operations + // var tcs = new TaskCompletionSource(); + + // _parent.SynchronizationContext.Post((state) => + // { + // var taskCompletionSource = (TaskCompletionSource)state; + + // // Start the async operation on the UI thread (this call itself is synchronous) + // var asyncOperation = InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken); + + // // Handle the completion asynchronously + // asyncOperation.ContinueWith(task => + // { + // if (task.IsFaulted) + // { + // var exception = task.Exception?.InnerException ?? task.Exception; + // taskCompletionSource.TrySetException(exception); + // } + // else if (task.IsCanceled) + // { + // taskCompletionSource.TrySetCanceled(); + // } + // else + // { + // taskCompletionSource.TrySetResult(task.Result); + // } + // }, TaskContinuationOptions.ExecuteSynchronously); + + // }, tcs); + + // return await tcs.Task.ConfigureAwait(false); + // #else + // // For non-WINRT, keep the existing synchronous pattern + // var sendAuthorizeRequestWithTcs = new Action((tcs) => + // { + // try + // { + // var authResult = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + // ((TaskCompletionSource)tcs).TrySetResult(authResult); + // } + // catch (Exception e) + // { + // ((TaskCompletionSource)tcs).TrySetException(e); + // } + // }); + + // var tcs2 = new TaskCompletionSource(); + // _parent.SynchronizationContext.Post(new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); + // return await tcs2.Task.ConfigureAwait(false); + // #endif + // } + // else + // { + // using (var staTaskScheduler = new StaTaskScheduler(1)) + // { + // try + // { + // Task.Factory.StartNew( + // sendAuthorizeRequest, + // cancellationToken, + // TaskCreationOptions.None, + // staTaskScheduler).Wait(cancellationToken); + // } + // catch (AggregateException ae) + // { + // requestContext.Logger.ErrorPii(ae.InnerException); + // Exception innerException = ae.InnerExceptions[0]; + + // if (innerException is AggregateException exception) + // { + // innerException = exception.InnerExceptions[0]; + // } + + // throw innerException; + // } + // } + // } + // } + // else + // { + // #if WINRT + // await sendAuthorizeRequest().ConfigureAwait(false); + // #else + // sendAuthorizeRequest(); + // #endif + // } + + // return result; + // } + public async Task AcquireAuthorizationAsync( + Uri authorizationUri, + Uri redirectUri, + RequestContext requestContext, + CancellationToken cancellationToken) + { + AuthorizationResult result = null; + + #if WINRT + // For WinUI3, get the dispatcher queue from the current context + var currentDispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + + // Try to get the main window's dispatcher if available + DispatcherQueue mainDispatcher = null; + try + { + // In WinUI3, we need to get the dispatcher from the main window or current context + mainDispatcher = currentDispatcher ?? + Microsoft.UI.Xaml.Window.Current?.DispatcherQueue ?? + (_parent.OwnerWindow as Microsoft.UI.Xaml.Window)?.DispatcherQueue; + } + catch { - AuthorizationResult result = null; - var sendAuthorizeRequest = new Action(() => + // Fallback to current thread dispatcher + mainDispatcher = currentDispatcher; + } + + if (mainDispatcher != null && mainDispatcher.HasThreadAccess) + { + // We're already on the correct UI thread - create window directly + return await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); + } + else if (mainDispatcher != null) + { + // We need to marshal to the UI thread + var tcs = new TaskCompletionSource(); + + mainDispatcher.TryEnqueue(() => { - result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + try + { + var asyncOperation = InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken); + asyncOperation.ContinueWith(task => + { + if (task.IsFaulted) + { + var exception = task.Exception?.InnerException ?? task.Exception; + tcs.TrySetException(exception); + } + else if (task.IsCanceled) + { + tcs.TrySetCanceled(); + } + else + { + tcs.TrySetResult(task.Result); + } + }, TaskContinuationOptions.ExecuteSynchronously); + } + catch (Exception ex) + { + tcs.TrySetException(ex); + } }); + + return await tcs.Task.ConfigureAwait(false); + } + else + { + // Fallback: No UI dispatcher available, use the traditional approach + var sendAuthorizeRequest = new Func(async () => + { + result = await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); + }); + #else + var sendAuthorizeRequest = new Action(() => + { + result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + }); + #endif if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) { if (_parent.SynchronizationContext != null) { + #if WINRT + // This is the fallback case for WinUI3 when no UI dispatcher is available + var tcs = new TaskCompletionSource(); + + _parent.SynchronizationContext.Post((state) => + { + var taskCompletionSource = (TaskCompletionSource)state; + + var asyncOperation = InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken); + asyncOperation.ContinueWith(task => + { + if (task.IsFaulted) + { + var exception = task.Exception?.InnerException ?? task.Exception; + taskCompletionSource.TrySetException(exception); + } + else if (task.IsCanceled) + { + taskCompletionSource.TrySetCanceled(); + } + else + { + taskCompletionSource.TrySetResult(task.Result); + } + }, TaskContinuationOptions.ExecuteSynchronously); + + }, tcs); + + return await tcs.Task.ConfigureAwait(false); + #else + // For non-WINRT, keep the existing synchronous pattern var sendAuthorizeRequestWithTcs = new Action((tcs) => { try { - result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); - ((TaskCompletionSource)tcs).TrySetResult(null); + var authResult = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + ((TaskCompletionSource)tcs).TrySetResult(authResult); } catch (Exception e) { - // Need to catch the exception here and put on the TCS which is the task we are waiting on so that - // the exception coming out of Authenticate is correctly thrown. - ((TaskCompletionSource)tcs).TrySetException(e); + ((TaskCompletionSource)tcs).TrySetException(e); } }); - var tcs2 = new TaskCompletionSource(); - - _parent.SynchronizationContext.Post( - new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); - await tcs2.Task.ConfigureAwait(false); + var tcs2 = new TaskCompletionSource(); + _parent.SynchronizationContext.Post(new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); + return await tcs2.Task.ConfigureAwait(false); + #endif } else { @@ -77,11 +400,8 @@ public async Task AcquireAuthorizationAsync( catch (AggregateException ae) { requestContext.Logger.ErrorPii(ae.InnerException); - // Any exception thrown as a result of running task will cause AggregateException to be thrown with - // actual exception as inner. Exception innerException = ae.InnerExceptions[0]; - // In MTA case, AggregateException is two layer deep, so checking the InnerException for that. if (innerException is AggregateException exception) { innerException = exception.InnerExceptions[0]; @@ -94,12 +414,18 @@ public async Task AcquireAuthorizationAsync( } else { + #if WINRT + await sendAuthorizeRequest().ConfigureAwait(false); + #else sendAuthorizeRequest(); + #endif } return result; - + #if WINRT } + #endif + } public Uri UpdateRedirectUri(Uri redirectUri) { @@ -107,6 +433,19 @@ public Uri UpdateRedirectUri(Uri redirectUri) return redirectUri; } +#if WINRT + private async Task InvokeEmbeddedWebviewAsync(Uri startUri, Uri endUri, CancellationToken cancellationToken) + { + var window = new WinUI3WindowWithWebView2( + _parent.OwnerWindow, + _parent?.EmbeddedWebviewOptions, + _requestContext.Logger, + startUri, + endUri); + + return await window.DisplayDialogAndInterceptUriAsync(cancellationToken).ConfigureAwait(false); + } +#else private AuthorizationResult InvokeEmbeddedWebview(Uri startUri, Uri endUri, CancellationToken cancellationToken) { using (var form = new WinFormsPanelWithWebView2( @@ -119,6 +458,6 @@ private AuthorizationResult InvokeEmbeddedWebview(Uri startUri, Uri endUri, Canc return form.DisplayDialogAndInterceptUri(cancellationToken); } } - +#endif } } diff --git a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUiFactory.cs b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUiFactory.cs index c680d1ef06..1162312fc2 100644 --- a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUiFactory.cs +++ b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUiFactory.cs @@ -5,7 +5,9 @@ using Microsoft.Identity.Client.ApiConfig.Parameters; using Microsoft.Identity.Client.Core; using Microsoft.Identity.Client.Internal; +#if !WINRT using Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi; +#endif using Microsoft.Identity.Client.Platforms.Shared.Desktop.OsBrowser; using Microsoft.Identity.Client.PlatformsCommon.Shared; using Microsoft.Identity.Client.UI; @@ -43,6 +45,7 @@ public IWebUI CreateAuthenticationDialog(CoreUIParent coreUIParent, WebViewPrefe AuthorityType authorityType = requestContext.ServiceBundle.Config.Authority.AuthorityInfo.AuthorityType; +#if !WINRT if (authorityType == AuthorityType.Aad) { requestContext.Logger.Info($"Using WebView1 embedded browser because the authority is {authorityType}. WebView2 does not provide SSO."); @@ -54,7 +57,7 @@ public IWebUI CreateAuthenticationDialog(CoreUIParent coreUIParent, WebViewPrefe requestContext.Logger.Info("Using WebView1 embedded browser because WebView2 is not available."); return new InteractiveWebUI(coreUIParent, requestContext); } - +#endif requestContext.Logger.Info("Using WebView2 embedded browser."); return new WebView2WebUi(coreUIParent, requestContext); } @@ -66,10 +69,12 @@ private static bool IsWebView2Available() string wv2Version = CoreWebView2Environment.GetAvailableBrowserVersionString(); return !string.IsNullOrEmpty(wv2Version); } +#if !WINRT catch (WebView2RuntimeNotFoundException) { return false; } +#endif catch (Exception ex) when (ex is BadImageFormatException || ex is DllNotFoundException) { throw new MsalClientException(MsalError.WebView2LoaderNotFound, MsalErrorMessage.WebView2LoaderNotFound, ex); diff --git a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml new file mode 100644 index 0000000000..9a3a0e5ac2 --- /dev/null +++ b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + diff --git a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml.cs b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml.cs new file mode 100644 index 0000000000..1869fdb74d --- /dev/null +++ b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml.cs @@ -0,0 +1,395 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#if WINRT +using Microsoft.Web.WebView2.Core; +using System; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.UI.Xaml; +using Microsoft.UI.Xaml.Controls; +using Microsoft.UI; +using Microsoft.UI.Windowing; +using Microsoft.Identity.Client.Core; +using Microsoft.Identity.Client.Platforms.Features.DesktopOs; +using Microsoft.Identity.Client.UI; +using WinRT.Interop; +using Windows.Graphics; +using Microsoft.UI.Xaml.Input; +using System.Diagnostics; + +namespace Microsoft.Identity.Client.Desktop.WebView2WebUi +{ + internal sealed partial class WinUI3WindowWithWebView2 : Window + { + private const int UIWidth = 566; + private readonly EmbeddedWebViewOptions _embeddedWebViewOptions; + private readonly ILoggerAdapter _logger; + private readonly Uri _startUri; + private readonly Uri _endUri; + private AuthorizationResult _result; + private TaskCompletionSource _dialogCompletionSource; + private CancellationToken _cancellationToken; + private Window _ownerWindow; + + /// + /// Initializes a new instance of the WinUI3WindowWithWebView2 class. + /// + /// The parent window that owns this authentication dialog. + /// Configuration options for the embedded WebView. + /// Logger instance for logging authentication flow events. + /// The initial URI to navigate to for authentication. + /// The redirect URI that signals completion of authentication. + public WinUI3WindowWithWebView2( + object ownerWindow, + EmbeddedWebViewOptions embeddedWebViewOptions, + ILoggerAdapter logger, + Uri startUri, + Uri endUri) + { + _embeddedWebViewOptions = embeddedWebViewOptions ?? EmbeddedWebViewOptions.GetDefaultOptions(); + _logger = logger ?? throw new ArgumentNullException(nameof(logger)); + _startUri = startUri ?? throw new ArgumentNullException(nameof(startUri)); + _endUri = endUri ?? throw new ArgumentNullException(nameof(endUri)); + + //If the ownerwindow is winforms, then this flow will not work, we need to switch to the winforms WebView2 control. + if (ownerWindow == null) + { + _ownerWindow = null; + } + else if (ownerWindow is Window window) + { + _ownerWindow = window; + } + else + { + throw new MsalException(MsalError.InvalidOwnerWindowType, + "Invalid owner window type. Expected types are IWin32Window or IntPtr (for window handle)."); + } + + // Then initialize component on the UI thread + // InvokeHandlingOwnerWindow(() => + // { + // XAML InitializeComponent() - this loads the XAML UI + this.InitializeComponent(); + + // Set up WebView2 event handlers + _webView2.CoreWebView2Initialized += WebView2_CoreWebView2Initialized; + _webView2.NavigationStarting += WebView2_NavigationStarting; + + ConfigureWindow(); + SetTitle(); + // }); + } + + private void WebView2_NavigationStarting(WebView2 sender, CoreWebView2NavigationStartingEventArgs e) + { + _logger.Info($"[WebView2Control] NavigationStarting fired for URL: {e.Uri}"); + + if (CheckForEndUrl(new Uri(e.Uri))) + { + _logger.Verbose(() => "[WebView2Control] Redirect URI reached. Stopping the interactive view"); + e.Cancel = true; + } + else + { + _logger.Verbose(() => "[WebView2Control] Navigating to " + e.Uri); + } + } + + private bool CheckForEndUrl(Uri url) + { + bool readyToClose = false; + + if (url.Authority.Equals(_endUri.Authority, StringComparison.OrdinalIgnoreCase) && + url.AbsolutePath.Equals(_endUri.AbsolutePath)) + { + _logger.Info("Redirect Uri was reached. Stopping WebView navigation..."); + _result = AuthorizationResult.FromUri(url.OriginalString); + readyToClose = true; + } + + if (!readyToClose && !EmbeddedUiCommon.IsAllowedIeOrEdgeAuthorizationRedirect(url)) + { + _logger.Error($"[WebView2Control] Redirection to url: {url} is not permitted - WebView2 will fail..."); + + _result = AuthorizationResult.FromStatus( + AuthorizationStatus.ErrorHttp, + MsalError.NonHttpsRedirectNotSupported, + MsalErrorMessage.NonHttpsRedirectNotSupported); + + readyToClose = true; + } + + if (readyToClose) + { + // Complete the dialog with the result + _dialogCompletionSource?.TrySetResult(_result); + + // Close the window + DispatcherQueue.TryEnqueue(() => this.Close()); + } + + return readyToClose; + } + + private void WebView2_CoreWebView2Initialized(WebView2 sender, CoreWebView2InitializedEventArgs args) + { + if (args.Exception != null) + { + _logger.Error($"[WebView2Control] CoreWebView2 initialization failed: {args.Exception.Message}"); + _dialogCompletionSource?.TrySetResult( + AuthorizationResult.FromStatus(AuthorizationStatus.ErrorHttp)); + return; + } + + _logger.Info("[WebView2Control] CoreWebView2InitializationCompleted"); + + _webView2.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false; + _webView2.CoreWebView2.Settings.AreDevToolsEnabled = false; + _webView2.CoreWebView2.Settings.AreHostObjectsAllowed = false; + _webView2.CoreWebView2.Settings.IsScriptEnabled = true; + _webView2.CoreWebView2.Settings.IsZoomControlEnabled = false; + _webView2.CoreWebView2.Settings.IsStatusBarEnabled = true; + _webView2.CoreWebView2.Settings.AreBrowserAcceleratorKeysEnabled = false; + + // Handle title changes + if (_embeddedWebViewOptions.Title == null) + { + _webView2.CoreWebView2.DocumentTitleChanged += CoreWebView2_DocumentTitleChanged; + } + else + { + this.Title = _embeddedWebViewOptions.Title; + } + + SetTitle(sender); + } + + /// + /// Displays the authentication dialog and waits for the user to complete the authentication flow. + /// + /// Cancellation token to cancel the authentication operation. + /// A task that represents the asynchronous authentication operation. The task result contains the authorization result. + public async Task DisplayDialogAndInterceptUriAsync(CancellationToken cancellationToken) + { + _cancellationToken = cancellationToken; + _dialogCompletionSource = new TaskCompletionSource(); + + // Register cancellation callback + using (cancellationToken.Register(CloseIfOpen)) + { + try + { + _logger.Info("Starting DisplayDialogAndInterceptUriAsync..."); + + // Activate the window first + InvokeHandlingOwnerWindow(() => + { + _logger.Info("Activating authentication window..."); + this.Activate(); + }); + + // Ensure WebView2 is properly initialized by creating a Core instance if needed + var initTcs = new TaskCompletionSource(); + + // Suppress VSTHRD101 warning - we're handling exceptions properly in this case +#pragma warning disable VSTHRD101 // Avoid using async lambda for a void returning delegate type + InvokeHandlingOwnerWindow(async () => + { + try + { + // Create explicit environment with proper user data folder + var userDataFolder = Environment.ExpandEnvironmentVariables("%UserProfile%/.msal/webview2/data"); + _logger.Info($"Initializing WebView2 with user data folder: {userDataFolder}"); + + // Create directory if it doesn't exist + System.IO.Directory.CreateDirectory(userDataFolder); + + // Create the environment - use the correct parameter order + var env = await CoreWebView2Environment.CreateWithOptionsAsync(null, userDataFolder, new CoreWebView2EnvironmentOptions()); + + // If the CoreWebView2 is not initialized yet, initialize it + if (_webView2.CoreWebView2 == null) + { + _logger.Info("WebView2 CoreWebView2 not initialized, initializing now..."); + + // This initializes the CoreWebView2 + await _webView2.EnsureCoreWebView2Async(env); + _logger.Info("WebView2 CoreWebView2 initialized successfully."); + } + + // Now navigate to the start URI + _logger.Info($"Starting navigation to: {_startUri}"); + _webView2.Source = _startUri; + + initTcs.TrySetResult(true); + } + catch (Exception ex) + { + _logger.Error($"Failed to initialize WebView2 or start navigation: {ex.Message}"); + initTcs.TrySetException(ex); + } + }); +#pragma warning restore VSTHRD101 + + // Wait for initialization and navigation to start + await initTcs.Task.ConfigureAwait(false); + + // Wait for the authentication to complete + _logger.Info("Waiting for authentication to complete..."); + var result = await _dialogCompletionSource.Task.ConfigureAwait(false); + _logger.Info($"Authentication completed with status: {result.Status}"); + return result; + } + catch (Exception ex) + { + _logger.Error($"Authentication failed with exception: {ex.Message}"); + return AuthorizationResult.FromStatus(AuthorizationStatus.ErrorHttp); + } + } + } + + /// + /// Some calls need to be made on the UI thread and this is the central place to check if we have an owner + /// window and if so, ensure we invoke on that proper thread. + /// + /// The action to execute on the UI thread + private void InvokeHandlingOwnerWindow(Action action) + { + // If we have an owner window, use its dispatcher queue + if (_ownerWindow != null && _ownerWindow.DispatcherQueue != null) + { + // If we're already on the UI thread of the owner window, execute directly + if (_ownerWindow.DispatcherQueue.HasThreadAccess) + { + action(); + } + else + { + // Otherwise, queue the action to run on the UI thread + _ownerWindow.DispatcherQueue.TryEnqueue(() => action()); + } + } + else + { + // Use our own dispatcher queue if owner window isn't available + if (DispatcherQueue.HasThreadAccess) + { + action(); + } + else + { + DispatcherQueue.TryEnqueue(() => action()); + } + } + } + + private void CloseIfOpen() + { + // Close the window if cancellation is requested + DispatcherQueue.TryEnqueue(() => + { + if (_dialogCompletionSource != null && !_dialogCompletionSource.Task.IsCompleted) + { + _dialogCompletionSource.TrySetResult(AuthorizationResult.FromStatus(AuthorizationStatus.UserCancel)); + } + this.Close(); + }); + } + + private void ConfigureWindow() + { + // Set window properties + // this.Title = "Microsoft Authentication Library"; + this.ExtendsContentIntoTitleBar = false; + + // Calculate window size based on screen dimensions + var displayArea = DisplayArea.Primary; + int uiHeight = (int)(Math.Max(displayArea.WorkArea.Height, 160) * 0.7); + + // Set window size + this.AppWindow.Resize(new SizeInt32(UIWidth, uiHeight)); + + // Position window + if (_ownerWindow != null) + { + // Center relative to owner window + var ownerPos = _ownerWindow.AppWindow.Position; + var ownerSize = _ownerWindow.AppWindow.Size; + var centerX = ownerPos.X + (ownerSize.Width - UIWidth) / 2; + var centerY = ownerPos.Y + (ownerSize.Height - uiHeight) / 2; + this.AppWindow.Move(new PointInt32(centerX, centerY)); + } + else + { + // Center on screen + var centerX = (displayArea.WorkArea.Width - UIWidth) / 2; + var centerY = (displayArea.WorkArea.Height - uiHeight) / 2; + this.AppWindow.Move(new PointInt32(centerX, centerY)); + } + + this.Closed += (s, e) => + { + if (_result == null) + { + _result = AuthorizationResult.FromStatus(AuthorizationStatus.UserCancel); + } + + if (_dialogCompletionSource != null && !_dialogCompletionSource.Task.IsCompleted) + { + _dialogCompletionSource.TrySetResult(_result); + } + }; + + this.Activated += (s, e) => + { + // Ensure window stays on top if no owner + if (_ownerWindow == null) + { + this.AppWindow.IsShownInSwitchers = true; + } + }; + } + + private void SetTitle(WebView2 webView2 = null) + { + var packageDisplayName = "Microsoft Authentication Library"; + var webView2Version = (webView2 != null) ? " - " + GetWebView2Version(webView2) : string.Empty; + Title = $"{packageDisplayName}{webView2Version}"; + } + + private string GetWebView2Version(WebView2 webView2) + { + try + { + var runtimeVersion = webView2.CoreWebView2.Environment.BrowserVersionString; + + CoreWebView2EnvironmentOptions options = new CoreWebView2EnvironmentOptions(); + var targetVersionMajorAndRest = options.TargetCompatibleBrowserVersion; + var versionList = targetVersionMajorAndRest.Split('.'); + if (versionList.Length != 4) + { + return "Invalid SDK build version"; + } + var sdkVersion = versionList[2] + "." + versionList[3]; + + return $"{runtimeVersion}; {sdkVersion}"; + } + catch (Exception ex) + { + _logger.Warning($"Failed to get WebView2 version: {ex.Message}"); + return "Unknown"; + } + } + + private void CoreWebView2_DocumentTitleChanged(object sender, object e) + { + DispatcherQueue.TryEnqueue(() => + { + this.Title = _webView2.CoreWebView2.DocumentTitle ?? ""; + }); + } + } +} +#endif diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/AuthCodeRequestComponent.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/AuthCodeRequestComponent.cs index b2fb4bbd3e..0dc0d3c523 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/AuthCodeRequestComponent.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/AuthCodeRequestComponent.cs @@ -75,7 +75,8 @@ private async Task> FetchAuthCodeAndPkceInter Uri authorizationUri = authorizationTuple.Item1; string state = authorizationTuple.Item2; string codeVerifier = authorizationTuple.Item3; - + // FetchAuthCodeAndPkceInternalAsync and VerifyAuthorizationResult is called on one thread and AcquireAuthorizationAsync is called on a different thread + // due to this there are synchronization issues and the authorizationResult is null when VerifyAuthorizationResult is called. var authorizationResult = await webUi.AcquireAuthorizationAsync( authorizationUri, _requestParams.RedirectUri, From 967bdbb23e17e58b6720d6c9be6f80fec7f37d8d Mon Sep 17 00:00:00 2001 From: Dharshan BJ Date: Tue, 22 Jul 2025 21:28:25 -0700 Subject: [PATCH 02/42] working --- .../WebView2WebUi/WebView2WebUi.cs | 406 +++++++++--------- 1 file changed, 204 insertions(+), 202 deletions(-) diff --git a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs index d6dde793a6..c188b68919 100644 --- a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs +++ b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs @@ -137,214 +137,42 @@ public WebView2WebUi(CoreUIParent parent, RequestContext requestContext) // } - // public async Task AcquireAuthorizationAsync( - // Uri authorizationUri, - // Uri redirectUri, - // RequestContext requestContext, - // CancellationToken cancellationToken) - // { - // AuthorizationResult result = null; - - // #if WINRT - // var sendAuthorizeRequest = new Func(async () => - // { - // result = await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); - // }); - // #else - // var sendAuthorizeRequest = new Action(() => - // { - // result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); - // }); - // #endif - - // if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) - // { - // if (_parent.SynchronizationContext != null) - // { - // #if WINRT - // // For WINRT/WinUI3, use ContinueWith pattern for async operations - // var tcs = new TaskCompletionSource(); - - // _parent.SynchronizationContext.Post((state) => - // { - // var taskCompletionSource = (TaskCompletionSource)state; - - // // Start the async operation on the UI thread (this call itself is synchronous) - // var asyncOperation = InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken); - - // // Handle the completion asynchronously - // asyncOperation.ContinueWith(task => - // { - // if (task.IsFaulted) - // { - // var exception = task.Exception?.InnerException ?? task.Exception; - // taskCompletionSource.TrySetException(exception); - // } - // else if (task.IsCanceled) - // { - // taskCompletionSource.TrySetCanceled(); - // } - // else - // { - // taskCompletionSource.TrySetResult(task.Result); - // } - // }, TaskContinuationOptions.ExecuteSynchronously); - - // }, tcs); - - // return await tcs.Task.ConfigureAwait(false); - // #else - // // For non-WINRT, keep the existing synchronous pattern - // var sendAuthorizeRequestWithTcs = new Action((tcs) => - // { - // try - // { - // var authResult = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); - // ((TaskCompletionSource)tcs).TrySetResult(authResult); - // } - // catch (Exception e) - // { - // ((TaskCompletionSource)tcs).TrySetException(e); - // } - // }); - - // var tcs2 = new TaskCompletionSource(); - // _parent.SynchronizationContext.Post(new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); - // return await tcs2.Task.ConfigureAwait(false); - // #endif - // } - // else - // { - // using (var staTaskScheduler = new StaTaskScheduler(1)) - // { - // try - // { - // Task.Factory.StartNew( - // sendAuthorizeRequest, - // cancellationToken, - // TaskCreationOptions.None, - // staTaskScheduler).Wait(cancellationToken); - // } - // catch (AggregateException ae) - // { - // requestContext.Logger.ErrorPii(ae.InnerException); - // Exception innerException = ae.InnerExceptions[0]; - - // if (innerException is AggregateException exception) - // { - // innerException = exception.InnerExceptions[0]; - // } - - // throw innerException; - // } - // } - // } - // } - // else - // { - // #if WINRT - // await sendAuthorizeRequest().ConfigureAwait(false); - // #else - // sendAuthorizeRequest(); - // #endif - // } - - // return result; - // } - public async Task AcquireAuthorizationAsync( - Uri authorizationUri, - Uri redirectUri, - RequestContext requestContext, - CancellationToken cancellationToken) - { - AuthorizationResult result = null; - - #if WINRT - // For WinUI3, get the dispatcher queue from the current context - var currentDispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); - - // Try to get the main window's dispatcher if available - DispatcherQueue mainDispatcher = null; - try - { - // In WinUI3, we need to get the dispatcher from the main window or current context - mainDispatcher = currentDispatcher ?? - Microsoft.UI.Xaml.Window.Current?.DispatcherQueue ?? - (_parent.OwnerWindow as Microsoft.UI.Xaml.Window)?.DispatcherQueue; - } - catch + public async Task AcquireAuthorizationAsync( + Uri authorizationUri, + Uri redirectUri, + RequestContext requestContext, + CancellationToken cancellationToken) { - // Fallback to current thread dispatcher - mainDispatcher = currentDispatcher; - } + AuthorizationResult result = null; - if (mainDispatcher != null && mainDispatcher.HasThreadAccess) - { - // We're already on the correct UI thread - create window directly - return await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); - } - else if (mainDispatcher != null) - { - // We need to marshal to the UI thread - var tcs = new TaskCompletionSource(); - - mainDispatcher.TryEnqueue(() => - { - try - { - var asyncOperation = InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken); - asyncOperation.ContinueWith(task => - { - if (task.IsFaulted) - { - var exception = task.Exception?.InnerException ?? task.Exception; - tcs.TrySetException(exception); - } - else if (task.IsCanceled) - { - tcs.TrySetCanceled(); - } - else - { - tcs.TrySetResult(task.Result); - } - }, TaskContinuationOptions.ExecuteSynchronously); - } - catch (Exception ex) - { - tcs.TrySetException(ex); - } - }); - - return await tcs.Task.ConfigureAwait(false); - } - else - { - // Fallback: No UI dispatcher available, use the traditional approach + #if WINRT var sendAuthorizeRequest = new Func(async () => { result = await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); }); - #else - var sendAuthorizeRequest = new Action(() => - { - result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); - }); - #endif + #else + var sendAuthorizeRequest = new Action(() => + { + result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + }); + #endif if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) { if (_parent.SynchronizationContext != null) { - #if WINRT - // This is the fallback case for WinUI3 when no UI dispatcher is available + #if WINRT + // For WINRT/WinUI3, use ContinueWith pattern for async operations var tcs = new TaskCompletionSource(); - + _parent.SynchronizationContext.Post((state) => { var taskCompletionSource = (TaskCompletionSource)state; - + + // Start the async operation on the UI thread (this call itself is synchronous) var asyncOperation = InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken); + + // Handle the completion asynchronously asyncOperation.ContinueWith(task => { if (task.IsFaulted) @@ -361,11 +189,11 @@ public async Task AcquireAuthorizationAsync( taskCompletionSource.TrySetResult(task.Result); } }, TaskContinuationOptions.ExecuteSynchronously); - + }, tcs); - + return await tcs.Task.ConfigureAwait(false); - #else + #else // For non-WINRT, keep the existing synchronous pattern var sendAuthorizeRequestWithTcs = new Action((tcs) => { @@ -383,7 +211,7 @@ public async Task AcquireAuthorizationAsync( var tcs2 = new TaskCompletionSource(); _parent.SynchronizationContext.Post(new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); return await tcs2.Task.ConfigureAwait(false); - #endif + #endif } else { @@ -414,18 +242,192 @@ public async Task AcquireAuthorizationAsync( } else { - #if WINRT + #if WINRT await sendAuthorizeRequest().ConfigureAwait(false); - #else + #else sendAuthorizeRequest(); - #endif + #endif } return result; - #if WINRT } - #endif - } + + + // public async Task AcquireAuthorizationAsync( + // Uri authorizationUri, + // Uri redirectUri, + // RequestContext requestContext, + // CancellationToken cancellationToken) + // { + // AuthorizationResult result = null; + + // #if WINRT + // // For WinUI3, get the dispatcher queue from the current context + // var currentDispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); + + // // Try to get the main window's dispatcher if available + // DispatcherQueue mainDispatcher = null; + // try + // { + // // In WinUI3, we need to get the dispatcher from the main window or current context + // mainDispatcher = currentDispatcher ?? + // Microsoft.UI.Xaml.Window.Current?.DispatcherQueue ?? + // (_parent.OwnerWindow as Microsoft.UI.Xaml.Window)?.DispatcherQueue; + // } + // catch + // { + // // Fallback to current thread dispatcher + // mainDispatcher = currentDispatcher; + // } + + // if (mainDispatcher != null && mainDispatcher.HasThreadAccess) + // { + // // We're already on the correct UI thread - create window directly + // return await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); + // } + // else if (mainDispatcher != null) + // { + // // We need to marshal to the UI thread + // var tcs = new TaskCompletionSource(); + + // mainDispatcher.TryEnqueue(() => + // { + // try + // { + // var asyncOperation = InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken); + // asyncOperation.ContinueWith(task => + // { + // if (task.IsFaulted) + // { + // var exception = task.Exception?.InnerException ?? task.Exception; + // tcs.TrySetException(exception); + // } + // else if (task.IsCanceled) + // { + // tcs.TrySetCanceled(); + // } + // else + // { + // tcs.TrySetResult(task.Result); + // } + // }, TaskContinuationOptions.ExecuteSynchronously); + // } + // catch (Exception ex) + // { + // tcs.TrySetException(ex); + // } + // }); + + // return await tcs.Task.ConfigureAwait(false); + // } + // else + // { + // // Fallback: No UI dispatcher available, use the traditional approach + // var sendAuthorizeRequest = new Func(async () => + // { + // result = await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); + // }); + // #else + // var sendAuthorizeRequest = new Action(() => + // { + // result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + // }); + // #endif + + // if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) + // { + // if (_parent.SynchronizationContext != null) + // { + // #if WINRT + // // This is the fallback case for WinUI3 when no UI dispatcher is available + // var tcs = new TaskCompletionSource(); + + // _parent.SynchronizationContext.Post((state) => + // { + // var taskCompletionSource = (TaskCompletionSource)state; + + // var asyncOperation = InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken); + // asyncOperation.ContinueWith(task => + // { + // if (task.IsFaulted) + // { + // var exception = task.Exception?.InnerException ?? task.Exception; + // taskCompletionSource.TrySetException(exception); + // } + // else if (task.IsCanceled) + // { + // taskCompletionSource.TrySetCanceled(); + // } + // else + // { + // taskCompletionSource.TrySetResult(task.Result); + // } + // }, TaskContinuationOptions.ExecuteSynchronously); + + // }, tcs); + + // return await tcs.Task.ConfigureAwait(false); + // #else + // // For non-WINRT, keep the existing synchronous pattern + // var sendAuthorizeRequestWithTcs = new Action((tcs) => + // { + // try + // { + // var authResult = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + // ((TaskCompletionSource)tcs).TrySetResult(authResult); + // } + // catch (Exception e) + // { + // ((TaskCompletionSource)tcs).TrySetException(e); + // } + // }); + + // var tcs2 = new TaskCompletionSource(); + // _parent.SynchronizationContext.Post(new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); + // return await tcs2.Task.ConfigureAwait(false); + // #endif + // } + // else + // { + // using (var staTaskScheduler = new StaTaskScheduler(1)) + // { + // try + // { + // Task.Factory.StartNew( + // sendAuthorizeRequest, + // cancellationToken, + // TaskCreationOptions.None, + // staTaskScheduler).Wait(cancellationToken); + // } + // catch (AggregateException ae) + // { + // requestContext.Logger.ErrorPii(ae.InnerException); + // Exception innerException = ae.InnerExceptions[0]; + + // if (innerException is AggregateException exception) + // { + // innerException = exception.InnerExceptions[0]; + // } + + // throw innerException; + // } + // } + // } + // } + // else + // { + // #if WINRT + // await sendAuthorizeRequest().ConfigureAwait(false); + // #else + // sendAuthorizeRequest(); + // #endif + // } + + // return result; + // #if WINRT + // } + // #endif + // } public Uri UpdateRedirectUri(Uri redirectUri) { From d30aa7b020ada96fdeb431d481593a74b831bcb3 Mon Sep 17 00:00:00 2001 From: Dharshan BJ Date: Wed, 23 Jul 2025 17:06:40 -0700 Subject: [PATCH 03/42] Code to create a seperate nuget for winui3 --- LibsAndSamples.sln | 44 +++ docs/msal-desktop-packages-guide.md | 144 +++++++++ ...soft.Identity.Client.Desktop.WinUI3.csproj | 62 ++++ .../WebView2WebUi/WebView2WebUi.cs | 302 +----------------- .../WinUI3WindowWithWebView2.xaml.cs | 2 +- .../Interactive/AuthCodeRequestComponent.cs | 3 +- .../NetCoreWinFormsWAM.csproj | 2 +- tests/devapps/WAM/WinUI3TestApp/App.xaml | 13 + tests/devapps/WAM/WinUI3TestApp/App.xaml.cs | 22 ++ .../devapps/WAM/WinUI3TestApp/MainWindow.xaml | 18 ++ .../WAM/WinUI3TestApp/MainWindow.xaml.cs | 45 +++ .../WAM/WinUI3TestApp/WinUI3TestApp.csproj | 22 ++ 12 files changed, 386 insertions(+), 293 deletions(-) create mode 100644 docs/msal-desktop-packages-guide.md create mode 100644 src/client/Microsoft.Identity.Client.Desktop.WinUI3/Microsoft.Identity.Client.Desktop.WinUI3.csproj create mode 100644 tests/devapps/WAM/WinUI3TestApp/App.xaml create mode 100644 tests/devapps/WAM/WinUI3TestApp/App.xaml.cs create mode 100644 tests/devapps/WAM/WinUI3TestApp/MainWindow.xaml create mode 100644 tests/devapps/WAM/WinUI3TestApp/MainWindow.xaml.cs create mode 100644 tests/devapps/WAM/WinUI3TestApp/WinUI3TestApp.csproj diff --git a/LibsAndSamples.sln b/LibsAndSamples.sln index ce76cc9fab..59aefe036a 100644 --- a/LibsAndSamples.sln +++ b/LibsAndSamples.sln @@ -112,6 +112,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Test.Per EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Client.Desktop", "src\client\Microsoft.Identity.Client.Desktop\Microsoft.Identity.Client.Desktop.csproj", "{A7679FF0-19E8-41E3-9F7C-F54235124CC4}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Client.Desktop.WinUI3", "src\client\Microsoft.Identity.Client.Desktop.WinUI3\Microsoft.Identity.Client.Desktop.WinUI3.csproj", "{B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "KerberosConsole", "tests\devapps\KerberosConsole\KerberosConsole.csproj", "{94F35780-220A-4C08-83B9-41168F7017CD}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Net5TestApp", "tests\devapps\Net5TestApp\Net5TestApp.csproj", "{998D38B3-344C-4F19-833E-6181B0834AF6}" @@ -968,6 +970,48 @@ Global {A7679FF0-19E8-41E3-9F7C-F54235124CC4}.Release|x64.Build.0 = Release|Any CPU {A7679FF0-19E8-41E3-9F7C-F54235124CC4}.Release|x86.ActiveCfg = Release|Any CPU {A7679FF0-19E8-41E3-9F7C-F54235124CC4}.Release|x86.Build.0 = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|Any CPU.ActiveCfg = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|Any CPU.Build.0 = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|ARM.ActiveCfg = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|ARM.Build.0 = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|ARM64.ActiveCfg = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|ARM64.Build.0 = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|iPhone.ActiveCfg = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|iPhone.Build.0 = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|iPhoneSimulator.ActiveCfg = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|iPhoneSimulator.Build.0 = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|x64.ActiveCfg = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|x64.Build.0 = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|x86.ActiveCfg = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug + MobileApps|x86.Build.0 = Debug + MobileApps|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|ARM.ActiveCfg = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|ARM.Build.0 = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|ARM64.ActiveCfg = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|ARM64.Build.0 = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|iPhone.ActiveCfg = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|iPhone.Build.0 = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|iPhoneSimulator.Build.0 = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|x64.ActiveCfg = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|x64.Build.0 = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|x86.ActiveCfg = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Debug|x86.Build.0 = Debug|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|Any CPU.Build.0 = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|ARM.ActiveCfg = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|ARM.Build.0 = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|ARM64.ActiveCfg = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|ARM64.Build.0 = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|iPhone.ActiveCfg = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|iPhone.Build.0 = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|iPhoneSimulator.Build.0 = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|x64.ActiveCfg = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|x64.Build.0 = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|x86.ActiveCfg = Release|Any CPU + {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5}.Release|x86.Build.0 = Release|Any CPU {94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|Any CPU.ActiveCfg = Debug + MobileApps|Any CPU {94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|Any CPU.Build.0 = Debug + MobileApps|Any CPU {94F35780-220A-4C08-83B9-41168F7017CD}.Debug + MobileApps|ARM.ActiveCfg = Debug + MobileApps|Any CPU diff --git a/docs/msal-desktop-packages-guide.md b/docs/msal-desktop-packages-guide.md new file mode 100644 index 0000000000..9d101e88e1 --- /dev/null +++ b/docs/msal-desktop-packages-guide.md @@ -0,0 +1,144 @@ +# MSAL.NET Desktop Packages Guide + +This guide explains how to choose the correct MSAL.NET Desktop package for your application. + +## Package Overview + +MSAL.NET now provides two separate Desktop packages to better support different Windows application types: + +### 1. Microsoft.Identity.Client.Desktop (Traditional) +- **Target Frameworks**: .NET Framework 4.6.2, .NET Core 3.1 +- **UI Technologies**: Windows Forms, WPF +- **WebView Implementation**: Windows Forms WebView2 +- **Use Case**: Traditional Windows desktop applications + +### 2. Microsoft.Identity.Client.Desktop.WinUI3 (Modern) +- **Target Frameworks**: .NET 8.0 Windows (net8.0-windows10.0.17763.0) +- **UI Technologies**: WinUI3, Windows App SDK +- **WebView Implementation**: WinUI3 WebView2 +- **Use Case**: Modern Windows applications built with WinUI3 + +## How to Choose the Right Package + +### For Windows Forms Applications +```xml + +``` + +**Example project file:** +```xml + + + WinExe + net8.0-windows + true + + + + + + +``` + +### For WPF Applications +```xml + +``` + +**Example project file:** +```xml + + + WinExe + net8.0-windows + true + + + + + + +``` + +### For WinUI3 Applications +```xml + +``` + +**Example project file:** +```xml + + + WinExe + net8.0-windows10.0.17763.0 + true + + + + + + + +``` + +## Framework Selection Logic + +### NuGet Package Selection +NuGet automatically selects the best compatible framework based on your application's target framework: + +| Your App's Target Framework | Selected Package | Implementation Used | +|------------------------------|------------------|-------------------| +| `net462`, `net472`, etc. | Desktop (net462) | Windows Forms WebView2 | +| `netcoreapp3.1` | Desktop (netcoreapp3.1) | Windows Forms WebView2 | +| `net5.0`, `net6.0`, `net7.0` | Desktop (netcoreapp3.1) | Windows Forms WebView2 | +| `net8.0`, `net8.0-windows` | Desktop (netcoreapp3.1) | Windows Forms WebView2 | +| `net8.0-windows10.0.17763.0` | WinUI3 (net8.0-windows10.0.17763.0) | WinUI3 WebView2 | + +### Key Benefits + +1. **No Framework Conflicts**: Each package targets the appropriate frameworks for its use case +2. **Clear Intent**: Developers explicitly choose the package that matches their application type +3. **Better Performance**: Each package is optimized for its specific UI technology +4. **Easier Troubleshooting**: Clear separation reduces compatibility issues + +## Migration Guide + +### If you're getting COM registration errors: + +1. **Check your target framework**: If you're targeting `net8.0-windows10.0.17763.0` but building a Windows Forms/WPF app, change to `net8.0-windows` +2. **Use the correct package**: Windows Forms/WPF apps should use `Microsoft.Identity.Client.Desktop` +3. **WinUI3 apps**: Use `Microsoft.Identity.Client.Desktop.WinUI3` and ensure your app is properly configured for WinUI3 + +### Breaking Changes +- Applications targeting `net8.0-windows10.0.17763.0` must now explicitly reference `Microsoft.Identity.Client.Desktop.WinUI3` +- The traditional Desktop package no longer includes the modern TFM target + +## Example Usage + +### Windows Forms Application +```csharp +var pca = PublicClientApplicationBuilder + .Create("your-client-id") + .WithAuthority("https://login.microsoftonline.com/common") + .WithRedirectUri("http://localhost") + .Build(); + +var result = await pca.AcquireTokenInteractive(scopes) + .WithParentActivityOrWindow(this) // 'this' is your Windows Form + .ExecuteAsync(); +``` + +### WinUI3 Application +```csharp +var pca = PublicClientApplicationBuilder + .Create("your-client-id") + .WithAuthority("https://login.microsoftonline.com/common") + .WithRedirectUri("http://localhost") + .Build(); + +var result = await pca.AcquireTokenInteractive(scopes) + .WithParentActivityOrWindow(this) // 'this' is your WinUI3 Window + .ExecuteAsync(); +``` + +The key difference is that the WinUI3 package will properly handle the WinUI3 Window type and use the appropriate WebView2 implementation. diff --git a/src/client/Microsoft.Identity.Client.Desktop.WinUI3/Microsoft.Identity.Client.Desktop.WinUI3.csproj b/src/client/Microsoft.Identity.Client.Desktop.WinUI3/Microsoft.Identity.Client.Desktop.WinUI3.csproj new file mode 100644 index 0000000000..1929092f5f --- /dev/null +++ b/src/client/Microsoft.Identity.Client.Desktop.WinUI3/Microsoft.Identity.Client.Desktop.WinUI3.csproj @@ -0,0 +1,62 @@ + + + net8.0-windows10.0.17763.0 + true + false + true + $(DefineConstants);NET_CORE;WINRT + + $(MSBuildThisFileDirectory)../Microsoft.Identity.Client/ + AnyCPU + Debug;Release;Debug + MobileApps + + + + + $(MsalInternalVersion) + $(MicrosoftIdentityClientVersion) + + $(MicrosoftIdentityClientVersion) + + MSAL.NET extension for WinUI3 desktop applications + + This package contains WinUI3-specific binaries for using MSAL.NET with modern Windows applications built on WinUI3 and Windows App SDK. + + Microsoft Authentication Library Desktop MSAL WinUI3 Windows App SDK WebView2 + Microsoft Authentication Library Desktop WinUI3 + + + + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs index c188b68919..8c7207eda5 100644 --- a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs +++ b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs @@ -34,109 +34,6 @@ public WebView2WebUi(CoreUIParent parent, RequestContext requestContext) _requestContext = requestContext; } - // public async Task AcquireAuthorizationAsync( - // Uri authorizationUri, - // Uri redirectUri, - // RequestContext requestContext, - // CancellationToken cancellationToken) - // { - // AuthorizationResult result = null; - - // #if WINRT - // var sendAuthorizeRequest = new Func(async () => - // { - // result = await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); - // }); - // #else - // var sendAuthorizeRequest = new Action(() => - // { - // result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); - // }); - // #endif - - // if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) - // { - // if (_parent.SynchronizationContext != null) - // { - // #if WINRT - // var sendAuthorizeRequestWithTcs = new Func(async (tcs) => - // { - // try - // { - // result = await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); - // #else - // var sendAuthorizeRequestWithTcs = new Action((tcs) => - // { - // try - // { - // result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); - // #endif - // ((TaskCompletionSource)tcs).TrySetResult(null); - // } - // catch (Exception e) - // { - // // Need to catch the exception here and put on the TCS which is the task we are waiting on so that - // // the exception coming out of Authenticate is correctly thrown. - // ((TaskCompletionSource)tcs).TrySetException(e); - // } - // }); - - // var tcs2 = new TaskCompletionSource(); - - // _parent.SynchronizationContext.Post( - // #if WINRT - // new SendOrPostCallback((state) => - // { - // Task.Run(() => sendAuthorizeRequestWithTcs(state)); - // }), tcs2); - // #else - // new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); - // await tcs2.Task.ConfigureAwait(false); - // #endif - // } - // else - // { - // using (var staTaskScheduler = new StaTaskScheduler(1)) - // { - // try - // { - // Task.Factory.StartNew( - // sendAuthorizeRequest, - // cancellationToken, - // TaskCreationOptions.None, - // staTaskScheduler).Wait(cancellationToken); - // } - // catch (AggregateException ae) - // { - // requestContext.Logger.ErrorPii(ae.InnerException); - // // Any exception thrown as a result of running task will cause AggregateException to be thrown with - // // actual exception as inner. - // Exception innerException = ae.InnerExceptions[0]; - - // // In MTA case, AggregateException is two layer deep, so checking the InnerException for that. - // if (innerException is AggregateException exception) - // { - // innerException = exception.InnerExceptions[0]; - // } - - // throw innerException; - // } - // } - // } - // } - // else - // { - // #if WINRT - // await sendAuthorizeRequest().ConfigureAwait(false); - // #else - // sendAuthorizeRequest(); - // #endif - // } - - // return result; - - // } - public async Task AcquireAuthorizationAsync( Uri authorizationUri, Uri redirectUri, @@ -193,25 +90,29 @@ public async Task AcquireAuthorizationAsync( }, tcs); return await tcs.Task.ConfigureAwait(false); - #else - // For non-WINRT, keep the existing synchronous pattern +#else + // For non-WINRT, restore the original synchronous pattern var sendAuthorizeRequestWithTcs = new Action((tcs) => { try { - var authResult = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); - ((TaskCompletionSource)tcs).TrySetResult(authResult); + result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); + ((TaskCompletionSource)tcs).TrySetResult(null); } catch (Exception e) { - ((TaskCompletionSource)tcs).TrySetException(e); + // Need to catch the exception here and put on the TCS which is the task we are waiting on so that + // the exception coming out of Authenticate is correctly thrown. + ((TaskCompletionSource)tcs).TrySetException(e); } }); - var tcs2 = new TaskCompletionSource(); - _parent.SynchronizationContext.Post(new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); - return await tcs2.Task.ConfigureAwait(false); - #endif + var tcs2 = new TaskCompletionSource(); + + _parent.SynchronizationContext.Post( + new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); + await tcs2.Task.ConfigureAwait(false); +#endif } else { @@ -251,183 +152,6 @@ public async Task AcquireAuthorizationAsync( return result; } - - - // public async Task AcquireAuthorizationAsync( - // Uri authorizationUri, - // Uri redirectUri, - // RequestContext requestContext, - // CancellationToken cancellationToken) - // { - // AuthorizationResult result = null; - - // #if WINRT - // // For WinUI3, get the dispatcher queue from the current context - // var currentDispatcher = Microsoft.UI.Dispatching.DispatcherQueue.GetForCurrentThread(); - - // // Try to get the main window's dispatcher if available - // DispatcherQueue mainDispatcher = null; - // try - // { - // // In WinUI3, we need to get the dispatcher from the main window or current context - // mainDispatcher = currentDispatcher ?? - // Microsoft.UI.Xaml.Window.Current?.DispatcherQueue ?? - // (_parent.OwnerWindow as Microsoft.UI.Xaml.Window)?.DispatcherQueue; - // } - // catch - // { - // // Fallback to current thread dispatcher - // mainDispatcher = currentDispatcher; - // } - - // if (mainDispatcher != null && mainDispatcher.HasThreadAccess) - // { - // // We're already on the correct UI thread - create window directly - // return await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); - // } - // else if (mainDispatcher != null) - // { - // // We need to marshal to the UI thread - // var tcs = new TaskCompletionSource(); - - // mainDispatcher.TryEnqueue(() => - // { - // try - // { - // var asyncOperation = InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken); - // asyncOperation.ContinueWith(task => - // { - // if (task.IsFaulted) - // { - // var exception = task.Exception?.InnerException ?? task.Exception; - // tcs.TrySetException(exception); - // } - // else if (task.IsCanceled) - // { - // tcs.TrySetCanceled(); - // } - // else - // { - // tcs.TrySetResult(task.Result); - // } - // }, TaskContinuationOptions.ExecuteSynchronously); - // } - // catch (Exception ex) - // { - // tcs.TrySetException(ex); - // } - // }); - - // return await tcs.Task.ConfigureAwait(false); - // } - // else - // { - // // Fallback: No UI dispatcher available, use the traditional approach - // var sendAuthorizeRequest = new Func(async () => - // { - // result = await InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken).ConfigureAwait(false); - // }); - // #else - // var sendAuthorizeRequest = new Action(() => - // { - // result = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); - // }); - // #endif - - // if (Thread.CurrentThread.GetApartmentState() == ApartmentState.MTA) - // { - // if (_parent.SynchronizationContext != null) - // { - // #if WINRT - // // This is the fallback case for WinUI3 when no UI dispatcher is available - // var tcs = new TaskCompletionSource(); - - // _parent.SynchronizationContext.Post((state) => - // { - // var taskCompletionSource = (TaskCompletionSource)state; - - // var asyncOperation = InvokeEmbeddedWebviewAsync(authorizationUri, redirectUri, cancellationToken); - // asyncOperation.ContinueWith(task => - // { - // if (task.IsFaulted) - // { - // var exception = task.Exception?.InnerException ?? task.Exception; - // taskCompletionSource.TrySetException(exception); - // } - // else if (task.IsCanceled) - // { - // taskCompletionSource.TrySetCanceled(); - // } - // else - // { - // taskCompletionSource.TrySetResult(task.Result); - // } - // }, TaskContinuationOptions.ExecuteSynchronously); - - // }, tcs); - - // return await tcs.Task.ConfigureAwait(false); - // #else - // // For non-WINRT, keep the existing synchronous pattern - // var sendAuthorizeRequestWithTcs = new Action((tcs) => - // { - // try - // { - // var authResult = InvokeEmbeddedWebview(authorizationUri, redirectUri, cancellationToken); - // ((TaskCompletionSource)tcs).TrySetResult(authResult); - // } - // catch (Exception e) - // { - // ((TaskCompletionSource)tcs).TrySetException(e); - // } - // }); - - // var tcs2 = new TaskCompletionSource(); - // _parent.SynchronizationContext.Post(new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); - // return await tcs2.Task.ConfigureAwait(false); - // #endif - // } - // else - // { - // using (var staTaskScheduler = new StaTaskScheduler(1)) - // { - // try - // { - // Task.Factory.StartNew( - // sendAuthorizeRequest, - // cancellationToken, - // TaskCreationOptions.None, - // staTaskScheduler).Wait(cancellationToken); - // } - // catch (AggregateException ae) - // { - // requestContext.Logger.ErrorPii(ae.InnerException); - // Exception innerException = ae.InnerExceptions[0]; - - // if (innerException is AggregateException exception) - // { - // innerException = exception.InnerExceptions[0]; - // } - - // throw innerException; - // } - // } - // } - // } - // else - // { - // #if WINRT - // await sendAuthorizeRequest().ConfigureAwait(false); - // #else - // sendAuthorizeRequest(); - // #endif - // } - - // return result; - // #if WINRT - // } - // #endif - // } public Uri UpdateRedirectUri(Uri redirectUri) { diff --git a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml.cs b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml.cs index 1869fdb74d..71f0abc105 100644 --- a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml.cs +++ b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WinUI3WindowWithWebView2.xaml.cs @@ -89,7 +89,7 @@ private void WebView2_NavigationStarting(WebView2 sender, CoreWebView2Navigation if (CheckForEndUrl(new Uri(e.Uri))) { _logger.Verbose(() => "[WebView2Control] Redirect URI reached. Stopping the interactive view"); - e.Cancel = true; + e.Cancel = true; } else { diff --git a/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/AuthCodeRequestComponent.cs b/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/AuthCodeRequestComponent.cs index 0dc0d3c523..eaaed87cc5 100644 --- a/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/AuthCodeRequestComponent.cs +++ b/src/client/Microsoft.Identity.Client/Internal/Requests/Interactive/AuthCodeRequestComponent.cs @@ -75,8 +75,7 @@ private async Task> FetchAuthCodeAndPkceInter Uri authorizationUri = authorizationTuple.Item1; string state = authorizationTuple.Item2; string codeVerifier = authorizationTuple.Item3; - // FetchAuthCodeAndPkceInternalAsync and VerifyAuthorizationResult is called on one thread and AcquireAuthorizationAsync is called on a different thread - // due to this there are synchronization issues and the authorizationResult is null when VerifyAuthorizationResult is called. + var authorizationResult = await webUi.AcquireAuthorizationAsync( authorizationUri, _requestParams.RedirectUri, diff --git a/tests/devapps/WAM/NetCoreWinFormsWam/NetCoreWinFormsWAM.csproj b/tests/devapps/WAM/NetCoreWinFormsWam/NetCoreWinFormsWAM.csproj index 84a6d9f97c..38b35817bc 100644 --- a/tests/devapps/WAM/NetCoreWinFormsWam/NetCoreWinFormsWAM.csproj +++ b/tests/devapps/WAM/NetCoreWinFormsWam/NetCoreWinFormsWAM.csproj @@ -2,7 +2,7 @@ WinExe - net8.0-windows10.0.17763.0 + net8.0-windows true Debug;Release;Debug + MobileApps diff --git a/tests/devapps/WAM/WinUI3TestApp/App.xaml b/tests/devapps/WAM/WinUI3TestApp/App.xaml new file mode 100644 index 0000000000..a2ea500ce1 --- /dev/null +++ b/tests/devapps/WAM/WinUI3TestApp/App.xaml @@ -0,0 +1,13 @@ + + + + + + + + + diff --git a/tests/devapps/WAM/WinUI3TestApp/App.xaml.cs b/tests/devapps/WAM/WinUI3TestApp/App.xaml.cs new file mode 100644 index 0000000000..75a2762aba --- /dev/null +++ b/tests/devapps/WAM/WinUI3TestApp/App.xaml.cs @@ -0,0 +1,22 @@ +using Microsoft.UI.Xaml; +using Microsoft.Identity.Client; +using System; +using System.Threading.Tasks; + +namespace WinUI3TestApp; + +public partial class App : Application +{ + public App() + { + this.InitializeComponent(); + } + + protected override void OnLaunched(LaunchActivatedEventArgs args) + { + m_window = new MainWindow(); + m_window.Activate(); + } + + private Window m_window; +} diff --git a/tests/devapps/WAM/WinUI3TestApp/MainWindow.xaml b/tests/devapps/WAM/WinUI3TestApp/MainWindow.xaml new file mode 100644 index 0000000000..2ec42b3d71 --- /dev/null +++ b/tests/devapps/WAM/WinUI3TestApp/MainWindow.xaml @@ -0,0 +1,18 @@ + + + + + +