Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

WPF: Managing Automatic Login using Default Network Credential and Credential Storage in a WPF Application

0.00/5 (No votes)
27 Sep 2020 1  
WPF Authentication framework with credential manager
Authentication framework and credential storage using WPF and Credential Manager

Introduction

Here, in this article, I have created a simple WPF application that tries to authenticate a user against the WebApi service using Default Network credential. If successful, it is stored for all future webapi requests. If it fails, then we present a login window to capture credential and on successful authentication, save the same for future use. It also stores the credential on the system using the Credential Manager packages.

Background

In today's world of microservices architecture, all the business logic resides in services. Even the age old WPF desktop applications are not left untouched. Solutions have moved in a direction where the WPF desktop applications contain the presentation and data validation logic. In the backend, it interacts with web APIs which need to be secured using authentication and authorization framework.

When working with desktop application, the general preference is to use the default Windows authentication. But in some scenarios, users might want to use other credentials as well.

Using the Code

The application is built around the "CredentialManagement" nuget packages which provides a wrapper for the Windows Credential Management API.

The logic starts with the App.xaml.cs file where we call the CreateHttpClientHandler within the constructor. The CreateHttpClientHandler is a static method in the HttpClientFactory class which uses the DefaultNetworkCredentials to connect to a WebApi. The Url can be any service or one specifically created to test successful connection. Having a separate API for testing connection is a better approach as it reduces the load in the application server and also protects against malicious users.

The code of specific importance within the HttpClientFactory is:

private static async Task<bool> CreateHttpHandlerUsingWindowsDefaultCredentialsAsync()
        {
            try
            {
                var response = await StoreCredentialsIfDomainControllerIsAccessibleAsync
                               (CredentialCache.DefaultNetworkCredentials, false);
                if(!response.IsSuccessStatusCode)
                {
                    //server responds but windows credential cannot login
                    if(response.StatusCode==HttpStatusCode.Unauthorized)
                    {
                        return false;

                    }
                }
                return true;
            }
            catch(Exception e)
            {
                if (e.InnerException is WebException webException)
                {
                    if (webException.Response is HttpWebResponse webResponse)
                    {
                        if (webResponse.StatusCode == HttpStatusCode.Unauthorized)
                            return false;
                    }
                }
                throw;
            }
        }

        public static async Task<HttpResponseMessage> 
               StoreCredentialsIfDomainControllerIsAccessibleAsync(
            NetworkCredential credentials, bool rememberCredentials)
        {
            var uriForTestingConnection = $"{BaseAddress}<url for testing connection>";            

            var httpClient = new HttpClient(new HttpClientHandler 
                             { Credentials = credentials });
            var res = await httpClient.GetAsync(uriForTestingConnection);
            if (res.IsSuccessStatusCode)
            {                
                 HttpClientFactory hc = Instance;
                 hc.Credentials = credentials;

                 if (rememberCredentials)
                     CredentialUtil.SetCredentials
                     (BaseAddress, credentials.UserName, // store domain and username 
                                                         // in Credential.Username
                            credentials.Password, PersistanceType.Enterprise);
                 else
                     CredentialUtil.RemoveCredentials(BaseAddress);
            }           
            
            return res;
        }

The test to webApi connection is done in the StoreCredentialsIfDomainControllerIsAccessibleAsync method which add the defaultnetwork credentials to the HttpClientHandler, and calls the Get method. If a response is a Success code, then the credentials are stored using the CredentialUtil class. The WebApi calls are wrapped in Try Catch blocks to handle scenarios where the webApi might be down and hence returns an unsuccessful status code.

The CredentialUtil is a wrapper around the CredentialManagerNuget package which stores the UserName and Password mapped to a base URL. It contains methods for storage, retrieval and removal of passwords.

public static class CredentialUtil
    {
        public static string GetPasswordIfRemembered(string target)
        {
            var cm = new Credential {Target=target };
            cm.Load();
            return cm.Password;
        }

        public static string GetUserNameIfRemembered(string target)
        {
            var cm = new Credential { Target=target};
            cm.Load();
            return cm.Username;
        }

        public static bool SetCredentials(string target, string username, 
                      string password, PersistanceType persistenceType)
        {
            return new Credential
            {
                Target = target,
                Username = username,
                Password = password,
                PersistanceType = persistenceType
            }
            .Save();
        }

        public static bool RemoveCredentials(string target)
        {
            return new Credential { Target=target}.Delete();
        }
    }

When Login is unsuccessful with the default network credential, the Login Dialog is loaded. Users can provide their credentials to access the service, which when validated loads the main application page. In the attached sample, the Login view loading is done using the MVVMLight framework.

I have attached the implementation solution, which needs to be configured to update valid Api Urls/endpoints.

History

  • 27th September, 2020: Initial version

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here