Custom authorization in WPF
Posted: March 24, 2013 Filed under: .NET, C#, Security, WPF | Tags: .NET, C#, IIdentity, IPrincipal, MVVM, WPF, XAML 51 CommentsThis post provides a code sample on how to 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.
It is very common for business 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 post 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.
To get started, create a new WPF application in Visual Studio (File->New->Project), remove the automatically generated MainWindow.xaml, add a new class (Project->Add 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, we 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[] { }) { } } }
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; using 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.
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.
The next step is to implement the viewmodel 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 } }
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.
The DelegateCommand type used for the commands in the viewmodel are a common implementation of System.Windows.Input.ICommand that simply invokes delegates when executing and querying executable status. It doesn’t come with WPF but you can easy implement your own like below 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; 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() { if (CanExecuteChanged != null) CanExecuteChanged(this, EventArgs.Empty); } } }
With this in place, now add a new window called LoginWindow.xaml (Project->Add window) 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 admninistrators..."/> </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.
Code doesn’t work. Did you check this before publishing this article? From where does the class IViewModel coming from ?
If it work could you please provide the source, probably to SkyDrive
Hi Dan,
The IViewModel is just a marker interface for the view model class:
public interface IViewModel {}
Thanks. But now I get following errors for all 3 forms which implement IView
The name ‘InitializeComponent’ does not exist in the current context.
Forms? You should add three window classes to your solution in Visual Studio by selecting “Add Window” on the project menu: http://msdn.microsoft.com/en-us/library/bb546952(v=vs.90).aspx.
Then you just open the code-behind file (e.g. LoginWindow.xaml.cs) for each window to modify the partial class to implement the IView interface.
Yes, I mean Window ( not Form, I am from traditional windows developing background). Yes I Added a new window using Visual Studio, The problem was I cut and paste your code but forget to change namespace in .xaml file to match my project namespace. Now it runs well.
It would be nice if you update the article to include missing code “public interface IViewModel {}” so the others will have a smooth experience.
Thanks.
Thanks for your feedback Dan. I’ve now added the marker interface to the code above the view model class.
This is exactly the sort of thing I was looking for! Very in-depth and explained well – Thanks for putting it together. I have one question though. Within the explanation and putting a sample together, you predominantly use a window control to use the [PrincipalPermission(SecurityAction.Demand)]. Within my app, I want to prevent a user depending on their role access to certain controls, but I use UserControls. Is there an easy way of means to do this based on your example? I tried to but it ultimately crashes when you navigate to that page.
Hi Greg,
To prevent the application from crashing when you try to access a UserControl or a Window that is protected with the PrincipalPermissionAttribute and you are not authorized, you need to catch the SecurityException that is thrown. This is exactly what I am doing in the view model’s ShowView method in the sample code.
This is a great article! One suggestion I had received when discussing this with others is to replace SHA256Managed() with SHA256Cng() – if not running on WinXP.
Cannot implicitly convert type ‘WpfApplication.LoginWindow’ to ‘WpfApplication.IView’.
An explicit conversion exists (are you missing a cast?)
this Code doesn’t work: loginWindow = new LoginWindow(viewModel);
how to fix this error?
Thanks.
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;
loginWindow = new LoginWindow(viewModel);
loginWindow.Show();
}
ebia: The LoginWindow class must implement the IView interface as shown in the code.
Thanks Magnus Montin
Resolved Error when implemented IView to LoginWindow class.
but when run this project and entry User name:Mark in Username TextBox
doesn’t work and Reflection
and dont show AdminWindow
please Upload To your SkyDrive this Project
I am getting exception… Can you please share the code…?
I like to display the login screen as popup screen with Shell display in background. it is possible?
Never Authenticates?
Hi, man nice post and description step by step, however it never authenticates, can you revise that?
Great post. I got it working right away. I want to close the login window and add the logout button to a protected form. I am a bit new to MVVM so do I need to add the logout method to each of my ViewModels or should I just make logout public on the AuthenticationViewModel and use it from all my UI?
the [PrincipalPermission(SecurityAction.Demand)] is returning an error object reference not set to the instance of an object
Thank you for the article! It worked great for me :)
This is really nice article. Thanks!! :D
Thank you, nice tutorial!
Worked first time for me, excellent. Can I apply this to a database that a new asp.net project has created with users in already?
Error 1 Inconsistent accessibility: property type ‘WpfApplication1.DelegateCommand’ is less accessible than property ‘WpfApplication1.AuthenticationViewModel.LoginCommand’ c:\users\greez\documents\visual studio 2013\Projects\WpfApplication1\WpfApplication1\AuthenticationViewModel.cs 61 32 WpfApplication1
I can’t get past this
grisli: You must make the DelegateCommand class public when you expose a public DelegateCommand property from another class:
public class DelegateCommand : ICommand
{
…
}
Works great for me, thanks!
Thank you so much! I’m not a programmer I just fiddle with c# and VS, got it going with a newb app I made, only stole some parts of your code. Thanks again!
>AppDomain.CurrentDomain.SetThreadPrincipal(customPrincipal);
Thank you! You have no idea how crazy I was getting trying to use Thread.CurrentPrincipal.
Thank you! very much
Thank you :)
Excelent Article… thank you! very much
Thank you very much, I adapted the code to use an abstract class instead of an interface so the code is cleaner when dealing with windows that share this code:
public IViewModel ViewModel{
get { return DataContext as IViewModel; }
set { DataContext = value; }
}
Instead I defined an abstract class extending Window native class, so every window extends WindowBase instead of Window:
public abstract class WindowBase : Window{
public IViewModel ViewModel
{
get { return DataContext as IViewModel; }
set { DataContext = value; }
}
}
Thanks a lot for this entry!
Thanks for sharing, this is exactly what I was looking.
How would you suggest adding new user details? I want to adapt the code to use a local database, so would you suggest to make a separate method to add a user rather than adding to this code?
Hi Magnus Montin,
Wpf has a setting in properties –> services –> Enable User Windows authentication
Must this be enabled to use windows authentication? and your example above?
Hi,
Visual studio has a option in project [Properties]-[Services]- Enable client application services – Use windows authentication
Does this need to be checked for your tutorial above or is it only related to MVC
Hi there where can i find this source code besides this blog? Github?
How can I implement this using PRISM and Unity?
How can I retrive UserName and Email from identity?
Thanks, it is a great article just I don`t know whey you have defined AuthenticatedUser property while you are not using it anywhere!
Thanks, it is a great article . how to check permission in Convert
Hello, Can you explain how convert IVIew to something which maintains DialogShow method? Thank you.
Hello, Thanks a lot… very nice code…
Nice code, thanks. I had much fun playing around with it. But the CalculateHash method seems a little limited. And since there is no method to encrypt a password from cleartext I made a new service with Encrypt/Decrypt methods that works well. If anyone is interested you can check it out here https://gist.github.com/diddimar/52aeb7fe6d9613982ec592ae5dd0a1c7
Great Article and working correct all code without any error I have tested it
Great example!
But, How use with Entity? Pls Help
Thank you very much Dan. This is an excellent intro!
how can I apply this to a database SQL Server that project has created with users in already?
Hi Magnus Montin!
Nice jobb! Thank you so much!
One question, when adding a new role to a user, the user has to restart the application. Is the any way to add a refresh to handle that instead?
Thanks,
Tobias Johansson
Hi Magnus,
Nice Intro, thank you. I tried it with .Net Framework 4.7.2, it works fine. Then i have migratet to .Net Core 3.1. There I got an Null Object Reference in the authenticationViewModel at the property IsAuthenticated. The Thread.CurrentPrincipal is null. It seems that the behave of Thread.CurrentPrincipal iin .Net Core is different to .Net Framework. Do you have a hint for me how to implement Thread.CurrentPrincipal in .Net Core?
Hi Ralf,
The solution presented in this post is not compatible with .NET Core. The PrincipalPermissionAttribute attribute has no effect in .NET Core: https://docs.microsoft.com/en-us/dotnet/api/system.security.permissions.principalpermissionattribute. Regarding the Thread.CurrentPrincipal property, please read this: https://github.com/dotnet/corefx/issues/36562.