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

[Auth] Use custom IBrowser for OIDC/MSAL #2640

Open
kazo0 opened this issue Dec 9, 2024 · 2 comments
Open

[Auth] Use custom IBrowser for OIDC/MSAL #2640

kazo0 opened this issue Dec 9, 2024 · 2 comments
Labels

Comments

@kazo0
Copy link
Contributor

kazo0 commented Dec 9, 2024

Use something similar to https://github.com/IdentityModel/IdentityModel.OidcClient.Samples/blob/main/NetCoreConsoleClient/src/NetCoreConsoleClient/SystemBrowser.cs for a browser implementation that can support net-desktop targets

I believe @carldebilly has a modified version of this that can do away with the Kestrel dependency.

@kazo0 kazo0 added kind/enhancement New feature or request. area/authentication labels Dec 9, 2024
@kazo0
Copy link
Contributor Author

kazo0 commented Dec 9, 2024

Poking @carldebilly / @nickrandolph for anymore info that we need for this one.

Doing this will allow us to no longer rely on the WebAuthenticationBroker, correct?

@carldebilly
Copy link
Member

carldebilly commented Dec 9, 2024

Why would you want to remove the dependency on Web Auth Broker? It's a robust mechanism and less proprietary than the IBrowser interface. In the latest WinUI, Microsoft introduced a new OAuth2Manager in the latest version of WinAppSdk (1.7), so we should consider aligning with it.

Actually, I did something slightly different: instead of customizing the IBrowser interface specific to EntityModel.OidcClient (now renamed Duende.IdentityModel.OidcClient), I implemented a custom WebAuthenticationBroker within Uno (leveraging Uno's built-in API extensibility mechanisms) and used the AutoRedirectUri feature to correctly resolve the URI from the browser.

The missing piece is the custom Web Authentication Broker (WAB), which uses my "new" http server. This server is extremely lightweight, has no dependencies, and is far easier to use than Kestrel. Check it out here: https://www.nuget.org/packages/Yllibed.HttpServer/1.0.0-dev.10.ge2ac37743f#readme-body-tab

Code:

#if HAS_UNO_SKIA
using System.Diagnostics;
using System.Runtime.InteropServices;
using Windows.Security.Authentication.Web;
using Uno.AuthenticationBroker;
using Uno.Foundation.Extensibility;
using Yllibed.HttpServer;
using Yllibed.HttpServer.Handlers;

[assembly:
	ApiExtension(typeof(IWebAuthenticationBrokerProvider), typeof(SystemBrowserAuthBroker))]

namespace MyApp.Services.Auth;

public sealed class SystemBrowserAuthBroker : IWebAuthenticationBrokerProvider
{
	private Server? _server;

	private Uri? _serverRootUri;

	public Uri GetCurrentApplicationCallbackUri()
	{
		return new Uri(EnsureServerStarted().RootUri, "/auth-callback");
	}

	public async Task<WebAuthenticationResult> AuthenticateAsync(WebAuthenticationOptions options, Uri requestUri,
		Uri callbackUri, CancellationToken ct)
	{
		if (options.HasFlag(WebAuthenticationOptions.SilentMode))
		{
			throw new NotSupportedException("SilentMode is not supported by this broker.");
		}

		if (options.HasFlag(WebAuthenticationOptions.UseTitle))
		{
			throw new NotSupportedException("UseTitle is not supported by this broker.");
		}

		if (options.HasFlag(WebAuthenticationOptions.UseHttpPost))
		{
			throw new NotSupportedException("UseHttpPost is not supported by this broker.");
		}

		if (options.HasFlag(WebAuthenticationOptions.UseCorporateNetwork))
		{
			throw new NotSupportedException("UseCorporateNetwork is not supported by this broker.");
		}

		var (server, _) = EnsureServerStarted();
		var authCallbackHandler = new AuthCallbackHandler(callbackUri);
		using (server.RegisterHandler(authCallbackHandler))
		{
			OpenBrowser(requestUri);
			return await authCallbackHandler.WaitForCallbackAsync();
		}
	}

	private (Server Server, Uri RootUri) EnsureServerStarted()
	{
		if (_server is null || _serverRootUri is null)
		{
			_server = new Server();
			(_serverRootUri, _) = _server.Start();
		}

		return (_server, _serverRootUri);
	}

	public static void OpenBrowser(Uri uri)
	{
		var url = uri.AbsoluteUri;
		try
		{
			Process.Start(url);
		}
		catch
		{
			// hack because of this: https://github.com/dotnet/corefx/issues/10361
			if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
			{
				url = url.Replace("&", "^&");
				Process.Start(new ProcessStartInfo("cmd", $"/c start {url}") { CreateNoWindow = true });
			}
			else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux))
			{
				Process.Start("xdg-open", url);
			}
			else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX) ||
			         RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD))
			{
				Process.Start("open", url);
			}
			else
			{
				throw;
			}
		}
	}


	private sealed class AuthCallbackHandler(Uri callbackUri) : IHttpHandler
	{
		private readonly TaskCompletionSource<WebAuthenticationResult> _tcs = new();

		public Task HandleRequest(CancellationToken ct, IHttpServerRequest request, string relativePath)
		{
			if (request.Url.AbsolutePath.StartsWith(callbackUri.AbsolutePath, StringComparison.OrdinalIgnoreCase))
			{
				var result = new WebAuthenticationResult(
					request.Url.OriginalString,
					200,
					WebAuthenticationStatus.Success);

				_tcs.TrySetResult(result);
				request.SetResponse("text/plain", "Auth completed - you can close this browser now.");
			}

			return Task.CompletedTask;
		}

		public Task<WebAuthenticationResult> WaitForCallbackAsync()
		{
			return _tcs.Task;
		}
	}
}
#endif

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

2 participants