diff --git a/Directory.Packages.props b/Directory.Packages.props
index a58477f84e..f8487ea2d4 100644
--- a/Directory.Packages.props
+++ b/Directory.Packages.props
@@ -13,7 +13,9 @@
-
+
+
+
diff --git a/LibsAndSamples.sln b/LibsAndSamples.sln
index ce76cc9fab..4fb94a9ae7 100644
--- a/LibsAndSamples.sln
+++ b/LibsAndSamples.sln
@@ -112,6 +112,12 @@ 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}") = "WinUI3PackagedSampleApp", "tests\devapps\WinUI3PackagedSampleApp\WinUI3PackagedSampleApp.csproj", "{B9A98EC5-074E-4366-A475-A246D09C32EE}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "WinFormsTestApp", "tests\devapps\WinFormsTestApp\WinFormsTestApp.csproj", "{F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}"
+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 +974,114 @@ 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
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|Any CPU.ActiveCfg = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|Any CPU.Build.0 = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|ARM.ActiveCfg = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|ARM64.ActiveCfg = Debug|ARM64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|ARM64.Build.0 = Debug|ARM64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|iPhone.ActiveCfg = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|iPhoneSimulator.ActiveCfg = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|x64.ActiveCfg = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|x64.Build.0 = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|x86.ActiveCfg = Debug|x86
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug + MobileApps|x86.Build.0 = Debug|x86
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|Any CPU.ActiveCfg = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|Any CPU.Build.0 = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|ARM.ActiveCfg = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|ARM64.Build.0 = Debug|ARM64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|iPhone.ActiveCfg = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|iPhoneSimulator.ActiveCfg = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|x64.ActiveCfg = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|x64.Build.0 = Debug|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|x86.ActiveCfg = Debug|x86
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Debug|x86.Build.0 = Debug|x86
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|Any CPU.ActiveCfg = Release|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|Any CPU.Build.0 = Release|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|ARM.ActiveCfg = Release|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|ARM64.ActiveCfg = Release|ARM64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|ARM64.Build.0 = Release|ARM64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|iPhone.ActiveCfg = Release|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|iPhoneSimulator.ActiveCfg = Release|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|x64.ActiveCfg = Release|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|x64.Build.0 = Release|x64
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|x86.ActiveCfg = Release|x86
+ {B9A98EC5-074E-4366-A475-A246D09C32EE}.Release|x86.Build.0 = Release|x86
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|Any CPU.ActiveCfg = Debug|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|Any CPU.Build.0 = Debug|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|ARM.ActiveCfg = Debug|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|ARM64.ActiveCfg = Debug|ARM64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|ARM64.Build.0 = Debug|ARM64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|iPhone.ActiveCfg = Debug|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|x64.ActiveCfg = Debug|x64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|x64.Build.0 = Debug|x64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|x86.ActiveCfg = Debug|x86
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug + MobileApps|x86.Build.0 = Debug|x86
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|ARM64.ActiveCfg = Debug|ARM64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|ARM64.Build.0 = Debug|ARM64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|iPhone.ActiveCfg = Debug|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|iPhoneSimulator.ActiveCfg = Debug|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|x64.ActiveCfg = Debug|x64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|x64.Build.0 = Debug|x64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|x86.ActiveCfg = Debug|x86
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Debug|x86.Build.0 = Debug|x86
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|ARM.ActiveCfg = Release|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|ARM64.ActiveCfg = Release|ARM64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|ARM64.Build.0 = Release|ARM64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|iPhone.ActiveCfg = Release|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|iPhoneSimulator.ActiveCfg = Release|Any CPU
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|x64.ActiveCfg = Release|x64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|x64.Build.0 = Release|x64
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|x86.ActiveCfg = Release|x86
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D}.Release|x86.Build.0 = Release|x86
{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
@@ -1936,6 +2050,9 @@ Global
{743BEA88-1903-4AF0-A791-705774F4B99D} = {9B0B5396-4D95-4C15-82ED-DC22B5A3123F}
{F7993CD1-91C1-4E22-9BDA-EAD6D4FB1D36} = {9B0B5396-4D95-4C15-82ED-DC22B5A3123F}
{A7679FF0-19E8-41E3-9F7C-F54235124CC4} = {1A37FD75-94E9-4D6F-953A-0DABBD7B49E9}
+ {B8689FF1-20F9-4669-CF55-9B2E8B5F8DD5} = {1A37FD75-94E9-4D6F-953A-0DABBD7B49E9}
+ {B9A98EC5-074E-4366-A475-A246D09C32EE} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
+ {F8C7D894-8B2F-4A1E-9D3C-5E4F7B8A9C6D} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
{94F35780-220A-4C08-83B9-41168F7017CD} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
{998D38B3-344C-4F19-833E-6181B0834AF6} = {384BA371-F17F-4A70-9423-D54F71BB3FCB}
{CC07F293-91B9-45A3-AA3A-77885BBCB624} = {34BE693E-3496-45A4-B1D2-D3A0E068EEDB}
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..dd5172f1bd
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client.Desktop.WinUI3/Microsoft.Identity.Client.Desktop.WinUI3.csproj
@@ -0,0 +1,57 @@
+
+
+
+ net8.0-windows10.0.17763.0
+ true
+ true
+ $(DefineConstants);WINUI3
+
+ $(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.WinUI3/WebView2WebUi/WinUI3WindowWithWebView2.cs b/src/client/Microsoft.Identity.Client.Desktop.WinUI3/WebView2WebUi/WinUI3WindowWithWebView2.cs
new file mode 100644
index 0000000000..224a31d074
--- /dev/null
+++ b/src/client/Microsoft.Identity.Client.Desktop.WinUI3/WebView2WebUi/WinUI3WindowWithWebView2.cs
@@ -0,0 +1,403 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+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 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;
+
+ private WebView2 _webView2;
+ private ProgressRing _progressRing;
+
+ ///
+ /// 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 (ownerWindow == null)
+ {
+ _ownerWindow = null;
+ }
+ else if (ownerWindow is Window window)
+ {
+ _ownerWindow = window;
+ }
+ else
+ {
+ throw new MsalException(MsalError.InvalidOwnerWindowType,
+ "Invalid owner window type. Expected type is Window (for window handle).");
+ }
+
+ InitializeWindow();
+ }
+
+ private void InitializeWindow()
+ {
+ var mainGrid = new Grid();
+ mainGrid.RowDefinitions.Add(new RowDefinition { Height = new GridLength(1, GridUnitType.Star) });
+
+ _webView2 = new WebView2();
+ Grid.SetRow(_webView2, 0);
+ mainGrid.Children.Add(_webView2);
+
+ _progressRing = new ProgressRing
+ {
+ IsActive = true,
+ Visibility = Visibility.Collapsed,
+ Width = 50,
+ Height = 50,
+ HorizontalAlignment = HorizontalAlignment.Center,
+ VerticalAlignment = VerticalAlignment.Center
+ };
+ Grid.SetRow(_progressRing, 0);
+ mainGrid.Children.Add(_progressRing);
+
+ // Set window content
+ Content = mainGrid;
+
+ // 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...");
+
+ InvokeHandlingOwnerWindow(() =>
+ {
+ _logger.Info("Activating authentication window...");
+ this.Activate();
+ });
+
+ var initTcs = new TaskCompletionSource();
+
+#pragma warning disable VSTHRD101
+ InvokeHandlingOwnerWindow(async () =>
+ {
+ try
+ {
+ var userDataFolder = Environment.ExpandEnvironmentVariables("%UserProfile%/.msal/webview2/data");
+ _logger.Info($"Initializing WebView2 with user data folder: {userDataFolder}");
+
+ System.IO.Directory.CreateDirectory(userDataFolder);
+
+ var env = await CoreWebView2Environment.CreateWithOptionsAsync(null, userDataFolder, new CoreWebView2EnvironmentOptions());
+
+ if (_webView2.CoreWebView2 == null)
+ {
+ _logger.Info("WebView2 CoreWebView2 not initialized, initializing now...");
+
+ await _webView2.EnsureCoreWebView2Async(env);
+ _logger.Info("WebView2 CoreWebView2 initialized successfully.");
+ }
+
+ _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
+
+ await initTcs.Task.ConfigureAwait(false);
+
+ _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.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 ?? "";
+ });
+ }
+ }
+}
diff --git a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs
index f850ec5236..998a9562d3 100644
--- a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs
+++ b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUi.cs
@@ -12,6 +12,16 @@
using Microsoft.Identity.Client.UI;
using Microsoft.Identity.Client.Core;
+#if WINUI3
+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
{
internal class WebView2WebUi : IWebUI
@@ -32,15 +42,54 @@ public async Task AcquireAuthorizationAsync(
CancellationToken cancellationToken)
{
AuthorizationResult result = null;
+
+#if WINUI3
+ 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 WINUI3
+ var tcs = new TaskCompletionSource();
+
+ _parent.SynchronizationContext.Post((state) =>
+ {
+ var taskCompletionSource = (TaskCompletionSource)state;
+
+ 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
var sendAuthorizeRequestWithTcs = new Action