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((tcs) => { try @@ -61,6 +110,7 @@ public async Task AcquireAuthorizationAsync( _parent.SynchronizationContext.Post( new SendOrPostCallback(sendAuthorizeRequestWithTcs), tcs2); await tcs2.Task.ConfigureAwait(false); +#endif } else { @@ -68,11 +118,19 @@ public async Task AcquireAuthorizationAsync( { try { +#if WINUI3 + await Task.Factory.StartNew( + sendAuthorizeRequest, + cancellationToken, + TaskCreationOptions.None, + staTaskScheduler).Unwrap().ConfigureAwait(false); +#else Task.Factory.StartNew( sendAuthorizeRequest, cancellationToken, TaskCreationOptions.None, staTaskScheduler).Wait(cancellationToken); +#endif } catch (AggregateException ae) { @@ -94,7 +152,11 @@ public async Task AcquireAuthorizationAsync( } else { +#if WINUI3 + await sendAuthorizeRequest().ConfigureAwait(false); +#else sendAuthorizeRequest(); +#endif } return result; @@ -107,6 +169,19 @@ public Uri UpdateRedirectUri(Uri redirectUri) return redirectUri; } +#if WINUI3 + 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 +194,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..c9773664b3 100644 --- a/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUiFactory.cs +++ b/src/client/Microsoft.Identity.Client.Desktop/WebView2WebUi/WebView2WebUiFactory.cs @@ -5,12 +5,15 @@ using Microsoft.Identity.Client.ApiConfig.Parameters; using Microsoft.Identity.Client.Core; using Microsoft.Identity.Client.Internal; -using Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi; using Microsoft.Identity.Client.Platforms.Shared.Desktop.OsBrowser; using Microsoft.Identity.Client.PlatformsCommon.Shared; using Microsoft.Identity.Client.UI; using Microsoft.Web.WebView2.Core; +#if !WINUI3 +using Microsoft.Identity.Client.Platforms.Features.WinFormsLegacyWebUi; +#endif + namespace Microsoft.Identity.Client.Desktop.WebView2WebUi { internal class WebView2WebUiFactory : IWebUIFactory @@ -41,6 +44,11 @@ public IWebUI CreateAuthenticationDialog(CoreUIParent coreUIParent, WebViewPrefe coreUIParent.SystemWebViewOptions); } +#if WINUI3 + // In MAUI/packaged winapps, webview2 is available by default + requestContext.Logger.Info("Using WebView2 embedded browser."); + return new WebView2WebUi(coreUIParent, requestContext); +#else AuthorityType authorityType = requestContext.ServiceBundle.Config.Authority.AuthorityInfo.AuthorityType; if (authorityType == AuthorityType.Aad) @@ -57,6 +65,7 @@ public IWebUI CreateAuthenticationDialog(CoreUIParent coreUIParent, WebViewPrefe requestContext.Logger.Info("Using WebView2 embedded browser."); return new WebView2WebUi(coreUIParent, requestContext); +#endif } private static bool IsWebView2Available() @@ -66,10 +75,12 @@ private static bool IsWebView2Available() string wv2Version = CoreWebView2Environment.GetAvailableBrowserVersionString(); return !string.IsNullOrEmpty(wv2Version); } +#if !WINUI3 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/ApiConfig/AcquireTokenInteractiveParameterBuilder.cs b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenInteractiveParameterBuilder.cs index a287f89ac5..a2694db91d 100644 --- a/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenInteractiveParameterBuilder.cs +++ b/src/client/Microsoft.Identity.Client/ApiConfig/AcquireTokenInteractiveParameterBuilder.cs @@ -236,11 +236,32 @@ private AcquireTokenInteractiveParameterBuilder WithParentObject(object parent) if (parent is IntPtr intPtrWindow) { Parameters.UiParent.OwnerWindow = intPtrWindow; + return this; + } +#endif + +#if NET_CORE + // Handle WinUI3 Window objects (including derived types) + if (parent != null && IsWinUI3Window(parent)) + { + Parameters.UiParent.OwnerWindow = parent; } #endif return this; } +#if NET_CORE + private static readonly Type WinUIWindowType = Type.GetType("Microsoft.UI.Xaml.Window, Microsoft.WinUI"); + + /// + /// Checks if an object is a WinUI3 Window or derives from one + /// + private static bool IsWinUI3Window(object obj) + { + return obj != null && WinUIWindowType?.IsAssignableFrom(obj.GetType()) == true; + } +#endif + #if ANDROID /// /// Sets a reference to the current Activity that triggers the browser to be shown. Required diff --git a/src/client/Microsoft.Identity.Client/Properties/InternalsVisibleTo.cs b/src/client/Microsoft.Identity.Client/Properties/InternalsVisibleTo.cs index 7e96268f4c..769ebfcbf0 100644 --- a/src/client/Microsoft.Identity.Client/Properties/InternalsVisibleTo.cs +++ b/src/client/Microsoft.Identity.Client/Properties/InternalsVisibleTo.cs @@ -5,6 +5,7 @@ using Microsoft.Identity.Client; [assembly: InternalsVisibleTo("Microsoft.Identity.Client.Desktop" + KeyTokens.MSAL)] +[assembly: InternalsVisibleTo("Microsoft.Identity.Client.Desktop.WinUI3" + KeyTokens.MSAL)] [assembly: InternalsVisibleTo("Microsoft.Identity.Client.Broker" + KeyTokens.MSAL)] [assembly: InternalsVisibleTo("Microsoft.Identity.Test.Unit" + KeyTokens.MSAL)] diff --git a/tests/devapps/WinFormsTestApp/MainForm.cs b/tests/devapps/WinFormsTestApp/MainForm.cs new file mode 100644 index 0000000000..774a7132ef --- /dev/null +++ b/tests/devapps/WinFormsTestApp/MainForm.cs @@ -0,0 +1,191 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Linq; +using System.Threading.Tasks; +using System.Windows.Forms; +using Microsoft.Identity.Client; +using Microsoft.Identity.Client.Desktop; + +namespace WinFormsTestApp; + +public partial class MainForm : Form +{ + private IPublicClientApplication _pca = null!; + private CheckBox _useEmbeddedWebViewCheckBox = null!; + private Button _loginButton = null!; + private TextBox _resultTextBox = null!; + + public MainForm() + { + InitializeComponent(); + InitializeMsal(); + } + + private void InitializeMsal() + { + _pca = PublicClientApplicationBuilder + .Create("e3b9ad76-9763-4827-b088-80c7a7888f79") + .WithB2CAuthority("https://msidlabb2c.b2clogin.com/tfp/msidlabb2c.onmicrosoft.com/B2C_1_SISOPolicy/") + .WithRedirectUri("http://localhost") + .WithWindowsEmbeddedBrowserSupport() + .Build(); + } + + private void InitializeComponent() + { + Text = "MSAL.NET WinForms Test App"; + Size = new System.Drawing.Size(600, 500); + StartPosition = FormStartPosition.CenterScreen; + + // Main layout panel + var mainPanel = new TableLayoutPanel + { + Dock = DockStyle.Fill, + ColumnCount = 1, + RowCount = 4, + Padding = new Padding(20) + }; + + // Title label + var titleLabel = new Label + { + Text = "MSAL.NET WinForms Test App", + Font = new System.Drawing.Font("Segoe UI", 18F, System.Drawing.FontStyle.Bold), + TextAlign = System.Drawing.ContentAlignment.MiddleCenter, + Dock = DockStyle.Fill, + AutoSize = false + }; + + // WebView option checkbox + _useEmbeddedWebViewCheckBox = new CheckBox + { + Text = "Use Embedded WebView", + AutoSize = true, + Anchor = AnchorStyles.None + }; + + var checkBoxPanel = new Panel + { + Height = 30, + Dock = DockStyle.Fill + }; + checkBoxPanel.Controls.Add(_useEmbeddedWebViewCheckBox); + _useEmbeddedWebViewCheckBox.Left = (checkBoxPanel.Width - _useEmbeddedWebViewCheckBox.Width) / 2; + _useEmbeddedWebViewCheckBox.Top = (checkBoxPanel.Height - _useEmbeddedWebViewCheckBox.Height) / 2; + + // Login button + _loginButton = new Button + { + Text = "Login with MSAL", + Size = new System.Drawing.Size(150, 40), + Anchor = AnchorStyles.None + }; + _loginButton.Click += LoginButton_Click; + + var buttonPanel = new Panel + { + Height = 50, + Dock = DockStyle.Fill + }; + buttonPanel.Controls.Add(_loginButton); + _loginButton.Left = (buttonPanel.Width - _loginButton.Width) / 2; + _loginButton.Top = (buttonPanel.Height - _loginButton.Height) / 2; + + // Result text box + _resultTextBox = new TextBox + { + Text = "Click login to test MSAL authentication", + Multiline = true, + ScrollBars = ScrollBars.Vertical, + ReadOnly = true, + Dock = DockStyle.Fill, + Font = new System.Drawing.Font("Consolas", 9F) + }; + + // Add controls to main panel + mainPanel.Controls.Add(titleLabel, 0, 0); + mainPanel.Controls.Add(checkBoxPanel, 0, 1); + mainPanel.Controls.Add(buttonPanel, 0, 2); + mainPanel.Controls.Add(_resultTextBox, 0, 3); + + // Set row styles + mainPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 60)); + mainPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 40)); + mainPanel.RowStyles.Add(new RowStyle(SizeType.Absolute, 60)); + mainPanel.RowStyles.Add(new RowStyle(SizeType.Percent, 100)); + + // Handle resize to center controls + buttonPanel.Resize += (s, e) => + { + _loginButton.Left = (buttonPanel.Width - _loginButton.Width) / 2; + }; + + checkBoxPanel.Resize += (s, e) => + { + _useEmbeddedWebViewCheckBox.Left = (checkBoxPanel.Width - _useEmbeddedWebViewCheckBox.Width) / 2; + }; + + Controls.Add(mainPanel); + } + + private async void LoginButton_Click(object? sender, EventArgs e) + { + _loginButton.Enabled = false; + bool useEmbeddedWebView = _useEmbeddedWebViewCheckBox.Checked; + string authMethod = useEmbeddedWebView ? "Embedded WebView" : "System Browser"; + + try + { + _resultTextBox.Text = $"Authenticating using {authMethod}..."; + + var authBuilder = _pca.AcquireTokenInteractive(new[] { "https://msidlabb2c.onmicrosoft.com/msidlabb2capi/read" }); + + if (useEmbeddedWebView) + { + // Use embedded WebView with WinForms parent window + authBuilder = authBuilder.WithUseEmbeddedWebView(true) + .WithParentActivityOrWindow(this); + } + else + { + // Use system browser (default behavior) + authBuilder = authBuilder.WithUseEmbeddedWebView(false); + } + + var result = await authBuilder + .ExecuteAsync() + .ConfigureAwait(false); + + // Update UI on the UI thread + this.Invoke(new Action(() => + { + _resultTextBox.Text = $"🎉 Authentication Successful! (via {authMethod})\n\n" + + $"User: {result.Account.Username}\n" + + $"Display Name: {result.Account.GetTenantProfiles()?.FirstOrDefault()?.ClaimsPrincipal?.FindFirst("name")?.Value ?? "N/A"}\n" + + $"Tenant ID: {result.TenantId}\n" + + $"Account ID: {result.Account.HomeAccountId?.Identifier}\n" + + $"Token Type: {result.TokenType}\n" + + $"Expires On: {result.ExpiresOn:yyyy-MM-dd HH:mm:ss}\n" + + $"Scopes: {string.Join(", ", result.Scopes)}\n" + + $"Access Token (first 50 chars): {result.AccessToken.Substring(0, Math.Min(50, result.AccessToken.Length))}...\n" + + $"Correlation ID: {result.CorrelationId}"; + })); + } + catch (Exception ex) + { + this.Invoke(new Action(() => + { + _resultTextBox.Text = $"Authentication Failed! (via {authMethod})\n\nError: {ex.Message}\n\nException Type: {ex.GetType().Name}"; + })); + } + finally + { + this.Invoke(new Action(() => + { + _loginButton.Enabled = true; + })); + } + } +} diff --git a/tests/devapps/WinFormsTestApp/Program.cs b/tests/devapps/WinFormsTestApp/Program.cs new file mode 100644 index 0000000000..9d8a2deb65 --- /dev/null +++ b/tests/devapps/WinFormsTestApp/Program.cs @@ -0,0 +1,22 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using System; +using System.Windows.Forms; + +namespace WinFormsTestApp; + +internal static class Program +{ + /// + /// The main entry point for the application. + /// + [STAThread] + static void Main() + { + Application.SetHighDpiMode(HighDpiMode.SystemAware); + Application.EnableVisualStyles(); + Application.SetCompatibleTextRenderingDefault(false); + Application.Run(new MainForm()); + } +} diff --git a/tests/devapps/WinFormsTestApp/WinFormsTestApp.csproj b/tests/devapps/WinFormsTestApp/WinFormsTestApp.csproj new file mode 100644 index 0000000000..7f8a03beb8 --- /dev/null +++ b/tests/devapps/WinFormsTestApp/WinFormsTestApp.csproj @@ -0,0 +1,32 @@ + + + WinExe + net8.0-windows10.0.17763.0 + 10.0.17763.0 + WinFormsTestApp + true + x86;x64;ARM64 + win-x86;win-x64;win-arm64 + enable + latest + + + + + win-x86 + + + + win-x64 + + + + win-arm64 + + + + + + + + diff --git a/tests/devapps/WinUI3PackagedSampleApp/App.xaml b/tests/devapps/WinUI3PackagedSampleApp/App.xaml new file mode 100644 index 0000000000..d1f98aafc2 --- /dev/null +++ b/tests/devapps/WinUI3PackagedSampleApp/App.xaml @@ -0,0 +1,15 @@ + + + + + + + + + + + diff --git a/tests/devapps/WinUI3PackagedSampleApp/App.xaml.cs b/tests/devapps/WinUI3PackagedSampleApp/App.xaml.cs new file mode 100644 index 0000000000..7ede2e7702 --- /dev/null +++ b/tests/devapps/WinUI3PackagedSampleApp/App.xaml.cs @@ -0,0 +1,38 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +using Microsoft.UI.Xaml; +using Microsoft.Identity.Client; +using System; +using System.Threading.Tasks; + +namespace WinUI3PackagedSampleApp +{ + /// + /// Provides application-specific behavior to supplement the default Application class. + /// + public partial class App : Application + { + /// + /// Initializes the singleton application object. This is the first line of authored code + /// executed, and as such is the logical equivalent of main() or WinMain(). + /// + public App() + { + this.InitializeComponent(); + } + + /// + /// Invoked when the application is launched normally by the end user. Other entry points + /// will be used such as when the application is launched to open a specific file. + /// + /// Details about the launch request and process. + protected override void OnLaunched(Microsoft.UI.Xaml.LaunchActivatedEventArgs args) + { + m_window = new MainWindow(); + m_window.Activate(); + } + + private Window m_window; + } +} diff --git a/tests/devapps/WinUI3PackagedSampleApp/Assets/LockScreenLogo.scale-200.png b/tests/devapps/WinUI3PackagedSampleApp/Assets/LockScreenLogo.scale-200.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/devapps/WinUI3PackagedSampleApp/Assets/SplashScreen.scale-200.png b/tests/devapps/WinUI3PackagedSampleApp/Assets/SplashScreen.scale-200.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/devapps/WinUI3PackagedSampleApp/Assets/Square150x150Logo.scale-200.png b/tests/devapps/WinUI3PackagedSampleApp/Assets/Square150x150Logo.scale-200.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/devapps/WinUI3PackagedSampleApp/Assets/Square44x44Logo.scale-200.png b/tests/devapps/WinUI3PackagedSampleApp/Assets/Square44x44Logo.scale-200.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/devapps/WinUI3PackagedSampleApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png b/tests/devapps/WinUI3PackagedSampleApp/Assets/Square44x44Logo.targetsize-24_altform-unplated.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/devapps/WinUI3PackagedSampleApp/Assets/StoreLogo.png b/tests/devapps/WinUI3PackagedSampleApp/Assets/StoreLogo.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/devapps/WinUI3PackagedSampleApp/Assets/Wide310x150Logo.scale-200.png b/tests/devapps/WinUI3PackagedSampleApp/Assets/Wide310x150Logo.scale-200.png new file mode 100644 index 0000000000..e69de29bb2 diff --git a/tests/devapps/WinUI3PackagedSampleApp/MainWindow.xaml b/tests/devapps/WinUI3PackagedSampleApp/MainWindow.xaml new file mode 100644 index 0000000000..003235da15 --- /dev/null +++ b/tests/devapps/WinUI3PackagedSampleApp/MainWindow.xaml @@ -0,0 +1,25 @@ + + + + + + + + + + + +