WPF: Implementing Custom Authentication And Authorization
Introduction
This article provides a step-by-step code sample on how you can implement your own custom authentication and authorization in a WPF application by implementing classes that derive from the IIdentity and IPrincipal interfaces and overriding the application thread’s default identity.
This article was originally published on my blog in March 2013: http://blog.magnusmontin.net/2013/03/24/custom-authorization-in-wpf/
It is very common for enterprise applications to provide access to data or resources based on the credentials supplied by the user and these kinds of applications typically check the role of a user and provide access to various resources based on that role. The .NET Framework uses the System.Security.Principal.IIdentity and System.Security.Principal.IPrincipal interfaces as the basis for authentication and authorization and by implementing these fairly simple interfaces you can apply your own custom authentication in your applications.
The sample code in this article uses the MVVM design pattern and the solution consists of a simple window with basic login and logout functionality and some buttons to display windows protected with the PrincipalPermissionAttribute, a simple authentication service class to authenticate users based on a supplied username and a password, the actual implementation of the interfaces and some additional helper classes and interfaces.
IIdentity
To get started, create a new WPF application in Visual Studio, add a new class called CustomIdentity and implement it as below.
using
System.Security.Principal;
namespace WpfApplication
{
public class CustomIdentity : IIdentity
{
public CustomIdentity(string name, string email, string[] roles)
{
Name = name;
Email = email;
Roles = roles;
}
public string Name { get; private set; }
public string Email { get; private set; }
public string[] Roles { get; private set; }
#region IIdentity Members
public string AuthenticationType { get { return "Custom authentication"; } }
public bool IsAuthenticated { get { return !string.IsNullOrEmpty(Name); } }
#endregion
}
}
The IIdentity interface encapsulates a user’s identity and the custom implementation above exposes three properties – Name, Email and Roles – to be passed to the constructor when an instance is created. Note that the implementation of the IIdentity.IsAuthenticated property means that a user is considered to be authenticated once the name property has been set.
Next, add an additional class called AnonymousIdentity that extends CustomIdentity to represent an unauthenticated user, i.e. a user with an empty name.
namespace WpfApplication
{
public class AnonymousIdentity : CustomIdentity
{
public AnonymousIdentity()
: base(string.Empty, string.Empty, new string[] { })
{ }
}
}
IPrincipal
Once we have the CustomIdentity class we need to implement a class that derives from IPrincipal why we add a new class called CustomPrincipal to the application.
using
System.Linq;
System.Security.Principal;
namespace
WpfApplication
{
public
class
CustomPrincipal : IPrincipal
{
private
CustomIdentity _identity; public
CustomIdentity Identity
{
get
{ return
_identity ?? new
AnonymousIdentity(); }
set
{ _identity = value; }
}
#region
IPrincipal Members
IIdentity
IPrincipal.Identity
{
get
{ return
this .Identity;
}
}
public
bool
IsInRole( string
role)
{
return
_identity.Roles.Contains(role);
}
#endregion
}
}
A principal has an identity associated with it and returns instances of this through the IPrincipal.Identity property. In the custom implementation above we provide our own Identity property to be able to set the principal’s identity to an instance of our CustomIdentity class. Note that until the property has been set, i.e. as long as the private member variable _identity is NULL, it will return an anonymous (unauthenticated) identity.
AuthenticationService
To be able to authenticate users we then add a simple AuthentationService class, along with an interface and a type for the return data, to validate credentials supplied by users. In a real world scenario you would probably store the credentials and any additional information associated with a user in a SQL Server database or some other persistent storage but in the demo sample implementation below the values are stored in a static list inside the class.
using System.Linq;
using System.Text;
using System.Security.Cryptography;
namespace WpfApplication
{
public interface IAuthenticationService
{
User AuthenticateUser(string username, string password);
}
public class AuthenticationService : IAuthenticationService
{
private class InternalUserData
{
public InternalUserData(string username, string email, string hashedPassword, string[] roles)
{
Username = username;
Email = email;
HashedPassword = hashedPassword;
Roles = roles;
}
public string Username
{
get;
private set;
}
public string Email
{
get;
private set;
}
public string HashedPassword
{
get;
private set;
}
public string[] Roles
{
get;
private set;
}
}
private static readonly List<InternalUserData> _users = new List<InternalUserData>()
{
new InternalUserData("Mark", "mark@company.com",
"MB5PYIsbI2YzCUe34Q5ZU2VferIoI4Ttd+ydolWV0OE=", new string[] { "Administrators" }),
new InternalUserData("John", "john@company.com",
"hMaLizwzOQ5LeOnMuj+C6W75Zl5CXXYbwDSHWW9ZOXc=", new string[] { })
};
public User AuthenticateUser(string username, string clearTextPassword)
{
InternalUserData userData = _users.FirstOrDefault(u => u.Username.Equals(username)
&& u.HashedPassword.Equals(CalculateHash(clearTextPassword, u.Username)));
if (userData == null)
throw new UnauthorizedAccessException("Access denied. Please provide some valid credentials.");
return new User(userData.Username, userData.Email, userData.Roles);
}
private string CalculateHash(string clearTextPassword, string salt)
{
// Convert the salted password to a byte array
byte[] saltedHashBytes = Encoding.UTF8.GetBytes(clearTextPassword + salt);
// Use the hash algorithm to calculate the hash
HashAlgorithm algorithm = new SHA256Managed();
byte[] hash = algorithm.ComputeHash(saltedHashBytes);
// Return the hash as a base64 encoded string to be compared to the stored password
return Convert.ToBase64String(hash);
}
}
public class User
{
public User(string username, string email, string[] roles)
{
Username = username;
Email = email;
Roles = roles;
}
public string Username
{
get;
set;
}
public string Email
{
get;
set;
}
public string[] Roles
{
get;
set;
}
}
}
As it’s considered a bad practice to store passwords in clear text for security reasons, each instance of the InternalUserData helper class contains a one-way hashed and salted password with both users in the sample code having a valid password identical to their username, e.g. Mark’s password is “Mark” and John’s is “John”. The private helper method “CalulcateHash” is used to hash the user supplied password before it’s compared to the one stored in the private list.
View Model
The next step is to implement the view model to expose the authentication service to the yet to be implemented login window. We add a new class called AuthenticationViewModel and implement it as below.
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Controls;
using System.Security;
namespace WpfApplication
{
public interface IViewModel { }
public class AuthenticationViewModel : IViewModel, INotifyPropertyChanged
{
private readonly IAuthenticationService _authenticationService;
private readonly DelegateCommand _loginCommand;
private readonly DelegateCommand _logoutCommand;
private readonly DelegateCommand _showViewCommand;
private string _username;
private string _status;
public AuthenticationViewModel(IAuthenticationService authenticationService)
{
_authenticationService = authenticationService;
_loginCommand = new DelegateCommand(Login, CanLogin);
_logoutCommand = new DelegateCommand(Logout, CanLogout);
_showViewCommand = new DelegateCommand(ShowView, null);
}
#region Properties
public string Username
{
get { return _username;}
set { _username = value; NotifyPropertyChanged("Username"); }
}
public string AuthenticatedUser {
get
{
if (IsAuthenticated)
return string.Format("Signed in as {0}. {1}",
Thread.CurrentPrincipal.Identity.Name,
Thread.CurrentPrincipal.IsInRole("Administrators") ? "You are an administrator!"
: "You are NOT a member of the administrators group.");
return "Not authenticated!";
}
}
public string Status
{
get { return _status; }
set { _status = value; NotifyPropertyChanged("Status"); }
}
#endregion
#region Commands
public DelegateCommand LoginCommand { get { return _loginCommand; } }
public DelegateCommand LogoutCommand { get { return _logoutCommand; } }
public DelegateCommand ShowViewCommand { get { return _showViewCommand; } }
#endregion
private void Login(object parameter)
{
PasswordBox passwordBox = parameter as PasswordBox;
string clearTextPassword = passwordBox.Password;
try
{
//Validate credentials through the authentication service
User user = _authenticationService.AuthenticateUser(Username, clearTextPassword);
//Get the current principal object
CustomPrincipal customPrincipal = Thread.CurrentPrincipal as CustomPrincipal;
if (customPrincipal == null)
throw new ArgumentException("The application's default thread principal must be set to a CustomPrincipal object on startup.");
//Authenticate the user
customPrincipal.Identity = new CustomIdentity(user.Username, user.Email, user.Roles);
//Update UI
NotifyPropertyChanged("AuthenticatedUser");
NotifyPropertyChanged("IsAuthenticated");
_loginCommand.RaiseCanExecuteChanged();
_logoutCommand.RaiseCanExecuteChanged();
Username = string.Empty; //reset
passwordBox.Password = string.Empty; //reset
Status = string.Empty;
}
catch (UnauthorizedAccessException)
{
Status = "Login failed! Please provide some valid credentials.";
}
catch (Exception ex)
{
Status = string.Format("ERROR: {0}", ex.Message);
}
}
private bool CanLogin(object parameter)
{
return !IsAuthenticated;
}
private void Logout(object parameter) {
CustomPrincipal customPrincipal = Thread.CurrentPrincipal as CustomPrincipal;
if (customPrincipal != null)
{
customPrincipal.Identity = new AnonymousIdentity();
NotifyPropertyChanged("AuthenticatedUser");
NotifyPropertyChanged("IsAuthenticated");
_loginCommand.RaiseCanExecuteChanged();
_logoutCommand.RaiseCanExecuteChanged();
Status = string.Empty;
}
}
private bool CanLogout(object parameter)
{
return IsAuthenticated;
}
public bool IsAuthenticated
{
get { return Thread.CurrentPrincipal.Identity.IsAuthenticated; }
}
private void ShowView(object parameter) {
try
{
Status = string.Empty;
IView view;
if (parameter == null)
view = new SecretWindow();
else
view = new AdminWindow();
view.Show();
}
catch (SecurityException)
{
Status = "You are not authorized!";
}
}
#region INotifyPropertyChanged Members
public event PropertyChangedEventHandler PropertyChanged;
private void NotifyPropertyChanged(string propertyName) {
if (PropertyChanged != null)
PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
}
#endregion
}
}
App.xaml
When a user clicks a login button in the view (the window), a command on the viewmodel executes to perform the actual authentication by validating the supplied credentials against our authentication service and, in case of a successful validation, setting the Identity property of the CustomPrincipal instance associated with the currently executing thread to an instance of our CustomIdentity class. For this to work, we must configure our WPF application to use our CustomPrincipal . This is done once when the application starts by overriding the OnStartup method in App.xaml.cs and removing the StartupUri attribute from the XAML.
<Application x:Class="WpfApplication.App"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Application.Resources>
</Application.Resources>
</Application>
using System;
using System.Windows;
namespace WpfApplication
{
/// <summary>
/// Interaction logic for App.xaml
/// </summary>
public partial class App : Application
{
protected override void OnStartup(StartupEventArgs e) {
//Create a custom principal with an anonymous identity at startup
CustomPrincipal customPrincipal = new CustomPrincipal();
AppDomain.CurrentDomain.SetThreadPrincipal(customPrincipal);
base.OnStartup(e);
//Show the login view
AuthenticationViewModel viewModel = new AuthenticationViewModel(new AuthenticationService());
IView loginWindow = new LoginWindow(viewModel);
loginWindow.Show();
}
}
}
It’s important to note that you must only call AppDomain.CurrentDomain.SetThreadPrincipal once during your application’s lifetime. If you try to call the same method again any time during the execution of the application you will get a PolicyException saying “Default principal object cannot be set twice”. Because of this it is not an option to reset the thread’s principal once its default identity has been initially set.
ICommand
The DelegateCommand type used for the commands in the viewmodel are a common implementation of the System.Windows.Input.ICommand interface that simply invokes delegates when executing and querying executable status. It doesn’t come with WPF but you can easily implement your own one or use the one provided by Prism, the framework and guidance for building WPF and Silverlight applications from the Microsoft Patterns and Practices Team.
using System;
using System.Windows.Input;
namespace WpfApplication
{
public class DelegateCommand : ICommand
{
private readonly Predicate<object> _canExecute;
private readonly Action<object> _execute;
public event EventHandler CanExecuteChanged
{
add { CommandManager.RequerySuggested += value; }
remove { CommandManager.RequerySuggested -= value; }
}
public DelegateCommand(Action<object> execute)
: this(execute, null)
{
}
public DelegateCommand(Action<object> execute, Predicate<object> canExecute)
{
_execute = execute;
_canExecute = canExecute;
}
public bool CanExecute(object parameter)
{
if (_canExecute == null)
return true;
return _canExecute(parameter);
}
public void Execute(object parameter)
{
_execute(parameter);
}
public void RaiseCanExecuteChanged()
{
CommandManager.InvalidateRequerySuggested();
}
}
}
View
With this in place, add a new window called LoginWindow.xaml to the application and implement the markup and code-behind as below.
<Window x:Class="WpfApplication.LoginWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="LoginWindow" Height="300" Width="600">
<Window.Resources>
<BooleanToVisibilityConverter x:Key="booleanToVisibilityConverter"/>
</Window.Resources>
<Grid>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<TextBlock Text="{Binding AuthenticatedUser}" Grid.Row="0" Grid.Column="0" Grid.ColumnSpan="2"
FontSize="14" HorizontalAlignment="Right" TextWrapping="Wrap" FontWeight="Bold"
Margin="2,2,2,2"/>
<TextBlock Text="Username:" Grid.Row="1" Grid.Column="0" />
<TextBlock Text="Password:" Grid.Row="2" Grid.Column="0" />
<TextBox Text="{Binding Username}" Grid.Row="1" Grid.Column="1" />
<PasswordBox x:Name="passwordBox" Grid.Row="2" Grid.Column="1" />
<StackPanel Orientation="Horizontal" Grid.Row="3" Grid.Column="1">
<Button Content="Log in" Command="{Binding LoginCommand, Mode=OneTime}"
CommandParameter="{Binding ElementName=passwordBox}"
HorizontalAlignment="Center"/>
<Button Content="Log out" Command="{Binding LogoutCommand, Mode=OneTime}"
Visibility="{Binding IsAuthenticated, Converter={StaticResource booleanToVisibilityConverter}}"
HorizontalAlignment="Center" Margin="2,0,0,0"/>
</StackPanel>
<TextBlock Text="{Binding Status}" Grid.Row="4" Grid.Column="1"
HorizontalAlignment="Center" VerticalAlignment="Center" Foreground="Red" TextWrapping="Wrap" />
<StackPanel Grid.Row="5" Grid.Column="1" VerticalAlignment="Center">
<Button Content="Show secret view" Command="{Binding ShowViewCommand}"
HorizontalAlignment="Center" />
<Button Content="Show admin view" Command="{Binding ShowViewCommand}" CommandParameter="Admin"
HorizontalAlignment="Center" Margin="2,2,0,0" />
</StackPanel>
</Grid>
</Window>
using System.Windows;
namespace WpfApplication
{
public interface IView
{
IViewModel ViewModel
{
get;
set;
}
void Show();
}
/// <summary>
/// Interaction logic for LoginWindow.xaml
/// </summary>
public partial class LoginWindow : Window, IView
{
public LoginWindow(AuthenticationViewModel viewModel)
{
ViewModel = viewModel;
InitializeComponent();
}
#region IView Members
public IViewModel ViewModel
{
get { return DataContext as IViewModel; }
set { DataContext = value; }
}
#endregion
}
}
The last step will be to add some protected views to able to verify that the authorization works as expected. The first one, called SecretWindow below, will be accessible by all authenticated users regardless of which group(s) they belong to, i.e. no roles are specified for the PrincipalPermissionAttribute, while the second one will by accessible only for members of the administrator group. Remember that the users and their respective group belongings are defined within the AuthenticationService.
using System.Windows;
using System.Security.Permissions;
namespace WpfApplication
{
/// <summary>
/// Interaction logic for SecretWindow.xaml
/// </summary>
[PrincipalPermission(SecurityAction.Demand)]
public partial class SecretWindow : Window, IView
{
public SecretWindow() {
InitializeComponent();
}
#region IView Members
public IViewModel ViewModel {
get {
return DataContext as IViewModel;
}
set {
DataContext = value;
}
}
#endregion
}
}
<Window x:Class="WpfApplication.SecretWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="SecretWindow" Height="300" Width="300">
<Grid>
<TextBlock Text="This window is only accessible for authenticated users..."/>
</Grid>
</Window>
using System.Windows;
using System.Security.Permissions;
namespace WpfApplication
{
/// <summary>
/// Interaction logic for AdminWindow.xaml
/// </summary>
[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")]
public partial class AdminWindow : Window, IView
{
public AdminWindow() {
InitializeComponent();
}
#region IView Members
public IViewModel ViewModel {
get {
return DataContext as IViewModel;
}
set {
DataContext = value;
}
}
#endregion
}
}
<Window x:Class="WpfApplication.AdminWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="AdminWindow" Height="300" Width="300">
<Grid>
<TextBlock Text="This window is only accessible for administrators..."/>
</Grid>
</Window>
When compiling and running the sample code you will notice that none of the windows will be shown until the user has logged in at the top of the window and only when logged in as “Mark” you will be able to display the administrator window.