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)
{
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,
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