Share via


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.