Blog Post

Modern Work App Consult Blog
3 MIN READ

How to use Active Directory Authentication Library (ADAL) for .NET on .NET Core 3.0 WPF apps

KazukiOta's avatar
KazukiOta
Icon for Microsoft rankMicrosoft
Apr 05, 2019

This article is as of .NET Core 3.0 Preview 3.

In .NET Framework, we are using Active Directory Authentication Library for .NET to authenticate user.

WPF apps has been using the following method for authenticate user:

 

AuthenticationResult AuthenticationContext.AcquireTokenAsync(string resource, stirng clientId, Uri redirectUri, PlatformParameters parameters)

Like as below:

var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
var result = await authContext.AcquireTokenAsync("https://graph.microsoft.com", "your app id", new Uri("urn:ietf:wg:oauth:2.0:oob"), new PlatformParameters(PromptBehavior.Auto));
MessageBox.Show(result.AccessToken);

If you migrated an app that has above code to .NET Core 3.0 Preview 3, then a compile error would be occurred.

error CS1729: 'PlatformParameters' does not contain a constructor that takes 1 arguments

Remove the parameter of PlatformParameters constructor, the compile error will be fixed.

var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
var result = await authContext.AcquireTokenAsync("https://graph.microsoft.com", "your app id", new Uri("urn:ietf:wg:oauth:2.0:oob"), new PlatformParameters());
MessageBox.Show(result.AccessToken);

However, unfortunately NotImplementedException is occurred.

How to fix it?

In ADAL for .NET v5.0.2-preview, there is an ICustomWebUi interface for PlatformParameters constructor.

We can implement a custom Web user interface using the ICustomWebUi.

Let's implement the interface. At first, to use UWP WebView control for modern sign in experience.

WPF WebBrowser control still can use, however it is not enough for modern Web experience.

  • Add Microsoft.Toolkit.Wpf.UI.Controls.WebView package version 6.0.0-preview3
  • Update Microsoft.IdentityModel.Clients.ActiveDirectory package to version 5.0.2-preview

So, let's write a implementation for ICustomWebUi interface like as below:

 

class CustomWebUi : ICustomWebUi
{
    private readonly Dispatcher _dispatcher;

    public CustomWebUi(Dispatcher dispatcher)
    {
        _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher))
    }

    public Task<Uri> AcquireAuthorizationCodeAsync(Uri authorizationUri, Uri redirectUr
    {
        var tcs = new TaskCompletionSource<Uri>();

        _dispatcher.InvokeAsync(() =>
        {
            var webView = new WebView();
            var w = new Window
            {
                Title = "Auth",
                WindowStyle = WindowStyle.ToolWindow,
                Content = webView,
            };
            w.Loaded += (_, __) => webView.Navigate(authorizationUri);


            webView.NavigationCompleted += (_, e) =>
            {
                System.Diagnostics.Debug.WriteLine(e.Uri);
                if (e.Uri.Query.Contains("code="))
                {
                    tcs.SetResult(e.Uri);
                    w.DialogResult = true;
                    w.Close();
                }
                if (e.Uri.Query.Contains("error="))
                {
                    tcs.SetException(new Exception(e.Uri.Query));
                    w.DialogResult = false;
                    w.Close();
                }
            };
            webView.UnsupportedUriSchemeIdentified += (_, e) =>
            {
                if (e.Uri.Query.Contains("code="))
                {
                    tcs.SetResult(e.Uri);
                    w.DialogResult = true;
                    w.Close();
                }
                else
                {
                    tcs.SetException(new Exception($"Unknown error: {e.Uri}"));
                    w.DialogResult = false;
                    w.Close();
                }
            };

            if (w.ShowDialog() != true && !tcs.Task.IsCompleted)
            {
                tcs.SetException(new Exception("canceled"));
            }
        });

        return tcs.Task;
    }
}

Let's use the class at calling the AcquireTokenAsync method.

 

 

var authContext = new AuthenticationContext("https://login.microsoftonline.com/common");
var result = await authContext.AcquireTokenAsync("https://graph.microsoft.com",
    "your app id",
    new Uri("urn:ietf:wg:oauth:2.0:oob"),
    new PlatformParameters(PromptBehavior.Auto, new CustomWebUi(Dispatcher)));
MessageBox.Show(result.AccessToken);

Now, we can get an access token after finished AcquireTokenAsync method.

 

Sample code

You can check full version sample code at following repository.

https://github.com/runceel/dotnet-core-wpf-adal-sample

 

Happy coding!!

 

 

 

Updated Apr 15, 2019
Version 3.0

9 Comments

  • PatrickLu's avatar
    PatrickLu
    Former Employee

    Hi KazukiOta,

     

    Thanks for the great article! Do you have suggestion how to implement this for a console based App? In the console app (C#), it seems the Dispatcher object is not available...

     

     

  • ciacco's avatar
    ciacco
    Copper Contributor

    How can i apply this example in a .net core multiplatform evironment where there's no WindowsBase since it can run on linux, too?

  • altafJebran 

    Thank you for your comment.

    I have never used WinForms on .NET Core.

    I'll try this on WinForms on .NET Core after.

  • altafJebran's avatar
    altafJebran
    Copper Contributor

    Hello KazukiOta ,

     

    I am trying to replicate the same in windows forms and i have observed that latest version of .netcore 3 preview 4 and windows form tool kit (for web view) are not compatible with each other so i reduced the version of webview to 6.0.0-preview1.

     

    Now when i create a object

    new PlatformParameters(promptbehaviour, new CustomWebUi(DispatcherObject) it throws an error as it takes it as atype.

     

    Please ignore the spelling mistakes as i m typing from mobile. Please let me know if you have tried this on windows forms.

     

    Thanks

  • Thank you for a comment Babu Pillai.

    Dispatcher class is System.Windows.Threading.Dispatcher class.

    The class is in WindowsBase.dll.

     

    I've added a repository link at bottom of article for to be able to see full version code.

    Could you check the code?

    If you cann't fix an error, then please let me know.

     

  • Babu Pillai's avatar
    Babu Pillai
    Copper Contributor

    This is awesome but how can I get Dispatcher class.. Is not finding in WindowsBase.dll.  Thoughts?