Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Add back support for MSAL auth through WAM #2659

Merged
merged 6 commits into from
Jan 13, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 10 additions & 5 deletions doc/Learn/Authentication/HowTo-MsalAuthentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,26 +48,31 @@ uid: Uno.Extensions.Authentication.HowToMsalAuthentication
}
```

- Add the `MsalAuthenticationProvider` using the `AddMsal()` extension method which configures the `IAuthenticationBuilder` to use it.
- Use the `Configure` method overload that provides access to a `Window` instance. Add the `MsalAuthenticationProvider` using the `AddMsal()` extension method which configures the `IAuthenticationBuilder` to use it.

```csharp
private IHost Host { get; set; }

protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var builder = this.CreateBuilder(args)
.Configure(host =>
.Configure((host, window) =>
{
host
.UseAuthentication(builder =>
{
builder.AddMsal();
builder.AddMsal(window);
});
});
...
}
```

> [!IMPORTANT]
> The `AddMsal()` method requires a `Window` instance, which the `MsalAuthenticationProvider` uses to set up the authentication dialog. You can access the `Window` instance through the `Configure()` method overload that provides it.
kazo0 marked this conversation as resolved.
Show resolved Hide resolved
> **Note:** Failing to pass a valid `Window` instance could result in a `MsalClientException` with the message:
> *"Only loopback redirect uri is supported, but <your_redirect_uri> was found. Configure http://localhost or http://localhost:port both during app registration and when you create the PublicClientApplication object. See https://aka.ms/msal-net-os-browser for details."*

- The `IAuthenticationBuilder` is responsible for managing the lifecycle of the associated provider that was built.

- Because it is configured to use MSAL, the user will eventually be prompted to sign in to their Microsoft account when they use your application. `MsalAuthenticationProvider` will then store the user's access token in credential storage. The token will be automatically refreshed when it expires.
Expand Down Expand Up @@ -95,12 +100,12 @@ uid: Uno.Extensions.Authentication.HowToMsalAuthentication
protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var builder = this.CreateBuilder(args)
.Configure(host =>
.Configure((host, window) =>
{
host
.UseAuthentication(builder =>
{
builder.AddMsal(msal =>
builder.AddMsal(window, msal =>
msal
.Builder(msalBuilder =>
msalBuilder.WithClientId("161a9fb5-3b16-487a-81a2-ac45dcc0ad3b"))
Expand Down
34 changes: 34 additions & 0 deletions doc/Learn/UpdatingExtensions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
---
uid: Uno.Extensions.Migration
---

# Upgrading Extensions Version

## Upgrading to Extensions 5.2

### MSAL Authentication

When upgrading to Uno.Extensions 5.2 or later, you must update your MSAL authentication setup in the host configuration. The `AddMsal()` method now includes an additional parameter to specify the `Window` instance used by the `MsalAuthenticationProvider` to configure the authentication dialog. You can obtain the `Window` instance from the `Configure()` method overload that provides it:

```diff
private IHost Host { get; set; }

protected override void OnLaunched(LaunchActivatedEventArgs args)
{
var builder = this.CreateBuilder(args)
- .Configure(host =>
+ .Configure((host, window) =>
{
host
.UseAuthentication(builder =>
{
- builder.AddMsal();
+ builder.AddMsal(window);
});
});
...
}
```

Failing to pass a valid `Window` instance could result in a `MsalClientException` with the message:
*"Only loopback redirect uri is supported, but <your_redirect_uri> was found. Configure http://localhost or http://localhost:port both during app registration and when you create the PublicClientApplication object. See https://aka.ms/msal-net-os-browser for details."*
2 changes: 2 additions & 0 deletions doc/toc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@
href: xref:Uno.Extensions.Overview
- name: "How-To: Getting Started"
href: xref:Uno.Extensions.HowToGettingStarted
- name: "Upgrading Extensions Version"
href: xref:Uno.Extensions.Migration
- name: Authentication
href: Learn/Authentication/toc.yml
topicHref: xref:Uno.Extensions.Authentication.Overview
Expand Down
2 changes: 1 addition & 1 deletion src/Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
<PackageVersion Include="Microsoft.Extensions.Logging" Version="8.0.0" />
<PackageVersion Include="Microsoft.Identity.Client" Version="4.66.2" />
<PackageVersion Include="Microsoft.Identity.Client.Extensions.Msal" Version="4.66.2" />
<PackageVersion Include="Microsoft.Identity.Client.Desktop" Version="4.66.2" />
<PackageVersion Include="Microsoft.Identity.Client.Broker" Version="4.66.2" />
<PackageVersion Include="Microsoft.Maui.Controls" Version="8.0.3" />
<PackageVersion Include="Microsoft.Maui.Controls.Compatibility" Version="8.0.3" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.6.2" />
Expand Down
39 changes: 38 additions & 1 deletion src/Uno.Extensions.Authentication.MSAL/HostBuilderExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -21,11 +21,48 @@ public static class HostBuilderExtensions
/// <returns>
/// The <see cref="IAuthenticationBuilder"/> that was passed in.
/// </returns>
[Obsolete("This method is obsolete. Please use the AddMsal overload that accepts a 'Window' parameter to specify the authentication window. The overload without 'Window' will be removed in a future release.", false)]
eriklimakc marked this conversation as resolved.
Show resolved Hide resolved
public static IAuthenticationBuilder AddMsal(
this IAuthenticationBuilder builder,
Action<IMsalAuthenticationBuilder>? configure = default,
string name = MsalAuthenticationProvider.DefaultName)
{
return InternalAddMsal(builder, null, configure, name);
}

/// <summary>
/// Adds MSAL authentication to the specified <see cref="IAuthenticationBuilder"/>.
/// </summary>
/// <param name="window">
/// The Window to which the MSAL authentication provider will be attached.
/// </param>
/// <param name="builder">
/// The <see cref="IAuthenticationBuilder"/> to add MSAL authentication to.
/// </param>
/// <param name="configure">
/// A delegate which can be used to configure the MSAL authentication provider that will be built. Optional.
/// </param>
/// <param name="name">
/// The name of the authentication provider. This optional parameter defaults to "Msal".
/// </param>
/// <returns>
/// The <see cref="IAuthenticationBuilder"/> that was passed in.
/// </returns>
public static IAuthenticationBuilder AddMsal(
this IAuthenticationBuilder builder,
Window window,
Action<IMsalAuthenticationBuilder>? configure = default,
string name = MsalAuthenticationProvider.DefaultName)
{
return InternalAddMsal(builder, window, configure, name);
}

private static IAuthenticationBuilder InternalAddMsal(
this IAuthenticationBuilder builder,
Window? window,
Action<IMsalAuthenticationBuilder>? configure = default,
string name = MsalAuthenticationProvider.DefaultName)
{
#if !UNO_EXT_MSAL
return builder;
#else
Expand Down Expand Up @@ -53,7 +90,7 @@ public static IAuthenticationBuilder AddMsal(
(provider, settings) =>
{
provider = provider with { Name = name, Settings = settings };
provider.Build();
provider.Build(window);
return provider;
});
#endif
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#if WINDOWS
using Microsoft.Identity.Client.Desktop;
using Microsoft.Identity.Client.Broker;
#endif
using Uno.Extensions.Logging;
using LogLevel = Microsoft.Extensions.Logging.LogLevel;
Expand Down Expand Up @@ -27,7 +27,7 @@ internal record MsalAuthenticationProvider(
private IPublicClientApplication? _pca;
private string[]? _scopes;

public void Build()
public void Build(Window? window)
{
if (Logger.IsEnabled(LogLevel.Trace)) Logger.LogTraceMessage($"Building MSAL Provider");
var config = Configuration.Get(Name) ?? new MsalConfiguration();
Expand All @@ -50,7 +50,20 @@ public void Build()
}

#if WINDOWS
builder.WithWindowsEmbeddedBrowserSupport();

if (window is { })
{
builder.WithBroker(new BrokerOptions(BrokerOptions.OperatingSystems.Windows));
builder.WithParentActivityOrWindow(() =>
{
IntPtr hwnd = WinRT.Interop.WindowNative.GetWindowHandle(window);
return hwnd;
});
}
else
{
Logger.LogError("Error: Passing a Window instance is now required. Ensure a valid Window is provided via the .AddMSal overload that takes a Window parameter. Avoiding passing a Window could cause a MsalClientException (\"Only loopback redirect URIs are supported, but a non - loopback URI was found...\") to be thrown.");
}
#endif

builder.WithUnoHelpers();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
</ItemGroup>

<ItemGroup Condition="$(TargetFramework.Contains('windows10'))">
<PackageReference Include="Microsoft.Identity.Client.Desktop" />
<PackageReference Include="Microsoft.Identity.Client.Broker" />
</ItemGroup>

<ItemGroup Condition="'$(IsBrowserWasm)'!='true'">
Expand Down
10 changes: 8 additions & 2 deletions src/Uno.Extensions.Hosting.UI/ApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ namespace Uno.Extensions.Hosting;

internal record ApplicationBuilder(Application App, LaunchActivatedEventArgs Arguments, Assembly ApplicationAssembly) : IApplicationBuilder
{
private readonly List<Action<IHostBuilder>> _delegates = new List<Action<IHostBuilder>>();
private readonly List<Action<IHostBuilder, Window>> _delegates = [];

public IDictionary<object, object> Properties { get; } = new Dictionary<object, object>();

Expand All @@ -26,13 +26,19 @@ public IHost Build()
Environment.GetCommandLineArgs().Skip(1).ToArray());
foreach (var del in _delegates)
{
del(builder);
del(builder, Window);
}

return builder.Build();
}

public IApplicationBuilder Configure(Action<IHostBuilder> configureHost)
{
_delegates.Add((builder, window) => configureHost(builder));
return this;
}

public IApplicationBuilder Configure(Action<IHostBuilder, Window> configureHost)
{
_delegates.Add(configureHost);
return this;
Expand Down
9 changes: 8 additions & 1 deletion src/Uno.Extensions.Hosting.UI/IApplicationBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,14 @@ public interface IApplicationBuilder
IApplicationBuilder Configure(Action<IHostBuilder> configureHost);

/// <summary>
/// Invokes any supplied delegates passed to the <see cref="Configure"/> method
/// Adds a configuration delegate for the <see cref="IHostBuilder" /> and provides the Window instance
/// </summary>
/// <param name="configureHost">Configuration Delegate</param>
/// <returns>The <see cref="IApplicationBuilder" /></returns>
IApplicationBuilder Configure(Action<IHostBuilder, Window> configureHost);

/// <summary>
/// Invokes any supplied delegates passed to the Configure method
/// and then calls the internal Build on the <see cref="IHostBuilder" />
/// </summary>
/// <returns>The <see cref="IHost" /></returns>
Expand Down
9 changes: 7 additions & 2 deletions testing/TestHarness/TestHarness/BaseHostInitialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ public abstract class BaseHostInitialization : IHostInitialization
{
protected virtual string[] ConfigurationFiles => Array.Empty<string>();

public virtual IHost InitializeHost()
public virtual IHost InitializeHost(Window window)
{
var host = UnoHost
.CreateDefaultBuilder()
Expand All @@ -17,7 +17,7 @@ public virtual IHost InitializeHost()

.Use(builder => Navigation(builder))

.Use(builder => Custom(builder))
.Use(builder => Custom(builder, window))

.Use(builder => Serialization(builder))

Expand Down Expand Up @@ -49,6 +49,11 @@ protected virtual IHostBuilder Custom(IHostBuilder builder)
return builder;
}

protected virtual IHostBuilder Custom(IHostBuilder builder, Window window)
{
return Custom(builder);
}

protected virtual IHostBuilder Environment(IHostBuilder builder)
{
return builder
Expand Down
4 changes: 2 additions & 2 deletions testing/TestHarness/TestHarness/BaseTestSectionPage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -69,12 +69,12 @@ private async Task InitializeHost()
// Uncomment this delay to see the loading/splash view for longer
// The Navigation/Apps/Commerce example uses an ExtendedSplashScreen in CommerceMainPage
// await Task.Delay(5000);
return HostInit!.InitializeHost();
return HostInit!.InitializeHost(win);
}, navigationRoot: loadingView);
}
else
{
Host = await win.InitializeNavigationAsync(async ()=>HostInit!.InitializeHost(), navigationRoot: navigationRoot);
Host = await win.InitializeNavigationAsync(async ()=>HostInit!.InitializeHost(win), navigationRoot: navigationRoot);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,11 @@ public class MsalAuthenticationHostInit : BaseMsalHostInitialization
protected override string[] ConfigurationFiles => new string[] { "TestHarness.Ext.Authentication.MSAL.appsettings.msalauthentication.json" };


protected override IHostBuilder Custom(IHostBuilder builder)
protected override IHostBuilder Custom(IHostBuilder builder, Window window)
{
return base.Custom(builder)
.UseAuthentication(auth =>
auth.AddMsal(msal =>
auth.AddMsal(window, msal =>
msal
.Scopes(new[] { "Tasks.Read", "User.Read", "Tasks.ReadWrite" })
.Builder(msalBuilder =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ public class MsalAuthenticationMultiHostInit : BaseMsalHostInitialization
protected override string[] ConfigurationFiles => new string[] { "TestHarness.Ext.Authentication.Custom.appsettings.dummyjson.json",
"TestHarness.Ext.Authentication.MSAL.appsettings.msalauthentication.json",
"TestHarness.Ext.Authentication.MSAL.appsettings.multi.json"};
protected override IHostBuilder Custom(IHostBuilder builder)
protected override IHostBuilder Custom(IHostBuilder builder, Window window)
{
return base.Custom(builder)
.ConfigureServices((context, services) =>
Expand Down Expand Up @@ -103,7 +103,7 @@ protected override IHostBuilder Custom(IHostBuilder builder)
}
return default;
}), name: "CustomService")
.AddMsal(msal =>
.AddMsal(window, msal =>
msal
.Scopes(new[] { "Tasks.Read", "User.Read", "Tasks.ReadWrite" })
.Builder(msalBuilder =>
Expand All @@ -116,7 +116,7 @@ protected override IHostBuilder Custom(IHostBuilder builder)
// msalBuilder = msalBuilder.WithIosKeychainSecurityGroup(settings.KeychainSecurityGroup);
//}
, name: "MsalCode")
.AddMsal(name: "MsalConfig")
.AddMsal(window, name: "MsalConfig")
);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,10 @@ public class MsalAuthenticationSettingsHostInit : BaseMsalHostInitialization
protected override string[] ConfigurationFiles => new string[] { "TestHarness.Ext.Authentication.MSAL.appsettings.msalauthentication.json",
"TestHarness.Ext.Authentication.MSAL.appsettings.msal.json"};

protected override IHostBuilder Custom(IHostBuilder builder)
protected override IHostBuilder Custom(IHostBuilder builder, Window window)
{
return base.Custom(builder)
.UseAuthentication(auth => auth.AddMsal());
.UseAuthentication(auth => auth.AddMsal(window));
}
}

Expand Down
2 changes: 1 addition & 1 deletion testing/TestHarness/TestHarness/IHostInitialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@

public interface IHostInitialization
{
IHost InitializeHost();
IHost InitializeHost(Window window);
}
Loading