Role-Based Security in Windows Apps

What's in a role?? Role is a term that is used to mean a lot of things in the world of software applications. The definition that we will stick to will be: A role is a context that a user will play in an application. For example, if you are writing an accounts application, you will probably allow the application to be used by clerks, managers, department heads and so on. Each of these constitute a role and there will be users who are members of the role. When users of a particular role access the system, the system responds by providing them with a specific set of permissions and the user is restricted to only those permissions. For example, if a user logs in as a clerk, he/she may be allowed to do only ledger entry, but not see the balance sheet of the company.
 
Roles provide a convenient way of grouping users together and managing them in a single unit. Permissions can be granted or denied to the role and the users in the role get affected. Roles have different meaning and similar equivalents in various applications. Some of the similar concepts that I know are:
  • Windows Groups. Microsoft Windows provides the concept of groups, which are similar to roles in that they allow addition of users to groups and then control permissions on those groups. For example, you can create a group called Managers and provide this group access to certain part of the file system. Any user who is a member of this role will get the appropriate access.
  • COM+ Roles. COM+ provides the notion of roles which are again containers for users. A COM+ role can be associated with a method, thus specifying that only members of the role can access that method. This sort of implementation provides a convenient way of seggregating application logic access based on users.
  • SQL Server Roles. SQL Server provides the notion of roles again to provide access to various parts of the system. SQL Server has many kinds of roles like: system roles, database roles and application roles. Each one of these category of roles provides a different level of access to a user. For example, if you are a member of one of the pre-defined set of system roles, you can do actions that are server wide. On the other hand, if you are a member of a database role, then your actions are restricted to the particular database only.

Roles are also called responsibilities in some systems, but the concept is the same. For example, the Oracle Applications Framework has the concept of responsibilities that can be used to provide access to various parts of the system. OK, having seen what roles are, how do we implement them in our system?? For example, can we define our own roles and then have permissions assigned to them?? Can our application intelligently modify itself based on who is accessing the system?? If all this is possible, how can I implement this in .NET??

These are the questions that this article will answer. In this article we are going to see the various .NET objects that help us to implement role-based security and we will see an example of how to implement one such system. We will also see some of the important methods and usages of the .NET objects. All the examples are based on Windows (smart-client) applications.

Role-based security in .NET can be implemented if you understand the following objects and what they are used for:

  • WindowsIdentity. This object encapsulates information about a Windows user. You can use this object to provide access to various functions of your application based on a Windows user. For example, you can write an application that requires all its users to be validated and belonging to the local administrator's group to access the application. One important method of this class is the GetCurrent method, which provides an object representing the current user.
  • WindowsPrincipal. This object represents a WindowsIdentity object along with all its memberships. What this means is, the principal object will contain all the group memberships of the windows user and this information can be used to implement authorization checks in your code. For example, we can check if the current user belongs to the local administrator's role using the members of this object. It is this object that allows us to implement role-based security for Windows users.
  • GenericIdentity. This object represents a generic user. For example, if your application stores its users in a database (because they are not domain users), you can create a GenericIdentity object that represents the logged-on user and then perform some checks with it. The reason why you have this object is that the programming model between Windows and generic users will be the same.
  • GenericPrincipal. This object represents a generic principal class. We saw earlier that a principal class contains the identity object and the list of memberships for that identity. If you are modelling a generic user using the GenericIdentity object, then you can store a list of roles corresponding to that user (possible fetched from a database) in this object and then implement authorization checks in your code. It is this object that allows us to implement role-based security for generic users.

All the above objects have a permissions object called PrincipalPermission. This object represents an particular identity and a particular membership (or role) that a principal object must have so that your code can execute its checks.

OK, those are some of the basics that you need to be aware of and its quite simple!! One question that you might now be having is, how do I implement all these concepts in my code?? There are two ways by which you can implement these concepts:

  • Imperative mode. In this mode, you use the Demand method of the associated permission object to determine whether the current principal represents your required condition.
  • Declarative mode. In this mode, you place attributes at the class or method level to indicate the check that you want. The runtime then checks whether the particular context is there.

In this article, we will see how to use the imperative mode.

OK, let's now see some implementation. First, we will see how to use the Windows* objects for implementing role-based security. After this, we will see how to use the Generic* objects. Assume that you have the following interface for your application.

 
Let us assume that the application provides two functions. One for computing payroll and the other for viewing the salaries of employees. Obviously viewing salaries requires more permissions than computing payroll. Since we are going to initially see how to use Windows permissions, we will define the following condition.
  • Compute Payroll can be accessed by any Windows user.
  • View Salaries can be accessed only if the user is a member of the Power Users group. If the user is not a member, then this function must be disabled.

Let us now see the code to implement these conditions. Since we want the buttons to be (en)disabled as soon as the application loads, the best place for us to write this code is in the form load event. Here is the source code (note that the designer generated code has been removed).


' The following imports are required for security checks and
' representing identities and principals
Imports System.Security.Principal
Imports System.Security.Permissions

Public Class frmWindows
    Inherits System.Windows.Forms.Form

#Region " Windows Form Designer generated code -- Removed for clarity"
#End Region

    Private Sub frmWindows_Load(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles MyBase.Load
        Dim myIdentity As WindowsIdentity
        Dim myPrincipal As WindowsPrincipal

        ' Point the identity to the current user
        myIdentity = WindowsIdentity.GetCurrent()

        ' Store the identity in the principal object. This gives us access
        ' to all the roles of the identity
        myPrincipal = New WindowsPrincipal(myIdentity)

        btnPayroll.Enabled = myPrincipal.IsInRole(WindowsBuiltInRole.User)
        btnSalary.Enabled = myPrincipal.IsInRole(WindowsBuiltInRole.PowerUser)

    End Sub
End Class

The code is quite simple. We import a couple of namespaces that provide access to the objects that we talked about earlier. We then declare a couple of variables that represent a Windows's identity and principal. We then get the current user who is running the application by using the GetCurrent method of the WindowsIdentity object. We then create a principal object using the identity that we just retrieved. At this point of time, the principal object represents the current user identity and the set of roles (or groups) to which the user belongs. Every button has an enabled property that can be set to True / False. We set the enabled property to the result of the IsInRole method of the principal object. Note that standard enumerations are provided for standard Windows groups. If you have a group that is not in this list, you can specify it as a string parameter. The ouput of this method call is either True / False indicating that the user belongs to the role or not. This, when set to the button enabled property will enable / disable the button.

That's all there is to it!! Now, if you run this program, based on your role membership in your system, either both the buttons are enabled or one of them (or both) might be disabled. For example, in my laptop (where I'm writing this article and running the sample), I'm not a member of the power users group and thus, the View Salaries button will be disabled.

Having seen how to use Windows* objects to implement role-based security, let us now see how to use Generic* objects to implement the same. When you use Generic* objects, the assumption is that the users of your application are not Windows users, but rather stored in your application itself (maybe a database). Also, roles for the user are also stored inside your application. In this context, you will have to create your own identity and principal objects and populate the roles by yourself. Let us assume that your application has the following interface:

Since users do not belong to Windows, we provide our own user-name and password text boxes for the user to enter information. When the user clicks on the Authenticate button, we will determine the user and assign the appropriate role to the user account. Now, when the user clicks any of the button in the operations area, the following conditions are to be implemented:

  • The Payroll button can be accessed only by the Managers role (note that this is not the Windows role, but a custom role)
  • The Reports button can only be accessed by the Clerks role (note that this is not the Windows role, but a custom role)

The black text box at the bottom of the screen, provides feedback when the user clicks the button. Let us now see the source code for the application. Since its a slightly complex application, we will see the source code in bits. First, let us see the list of namespaces that we have to import.


' The following namespaces are required to implement role based
' security for generic identities
Imports System.Security
Imports System.Security.Principal
Imports System.Security.Permissions
Imports System.Threading

After the user enters the user-name and password, the user will click on the Authenticate button. Let us see what happens when the user clicks this button.


Private Sub btnAuthenticate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnAuthenticate.Click
    Dim strRoles As String()

    ' Based on the user name, assign some static roles
    Select Case txtUserName.Text
        Case "srinivas"
            strRoles = New String() {"Managers"}
        Case "sampath"
            strRoles = New String() {"Clerks"}
        Case Else
            lblFeedback.Text = "Bad User Name"
    End Select

    ' Create the identity and the principal
    myIdentity = New GenericIdentity(txtUserName.Text)
    myPrincipal = New GenericPrincipal(myIdentity, strRoles)

    ' Assign the context
    Thread.CurrentPrincipal = myPrincipal

    lblFeedback.Text = "Authenticated..."
End Sub

Based on the user-name that is entered, we assign static roles to the strRoles array. In reality, at this point you will communicate with the database and check whether the user exists and if so, fetch the set of roles for the user. Since our example, is to just illustrate the concept, we just hardcode some values. After the array is populated, we then create a GenericIdentity object passing in the user-name that has been entered. Next, a GenericPrincipal object is created passing in the identity object and roles for that user as parameters. We then assign the principal object of the current thread to the created principal object, indicating that the user whom we created is running the application . Finally, the feedback text box is populated with a string indicating that the user has been authenticated.

Now the user can click any of the two operation buttons. Here is the source code for both the buttons.


Private Sub btnPayroll_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnPayroll.Click
    Dim myPermission As PrincipalPermission

    ' This function can be accessed only by administrators
    myPermission = New PrincipalPermission(Nothing, "Managers")
    Try
        myPermission.Demand()
        lblFeedback.Text = Thread.CurrentPrincipal.Identity.Name & " can do this operation."
    Catch ex As Exception
        lblFeedback.Text = ex.Message & ". " & Thread.CurrentPrincipal.Identity.Name & " does not have enough permissions."
    End Try
End Sub

Private Sub btnReports_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles btnReports.Click
    Dim myPermission As PrincipalPermission

    ' This function can be accessed only by normal users
    myPermission = New PrincipalPermission(Nothing, "Clerks")
    Try
        myPermission.Demand()
        lblFeedback.Text = Thread.CurrentPrincipal.Identity.Name & " can do this operation."
    Catch ex As Exception
        lblFeedback.Text = ex.Message & ". " & Thread.CurrentPrincipal.Identity.Name & " does not have enough permissions."
    End Try
End Sub

For the Payroll button, what we do is, create a PrincipalPermission object and pass to it two parameters. The first parameter indicates the identity that you want to check. Since we do not want to check for a specific identity (but rather any user), we pass Nothing as the parameter. This has the effect of checking for any identity. The second parameter is the role that the identity should have that we want to check. For the Payroll button, the role that we expect the identity to have is the Managers role. After creating the permission objectc, we use the demand method to check whether the identity has the appropriate role. This check is put inside a Try...Catch block. If the demand fails, we print out an error message that the current authenticated user (got by using Thread.CurrentPrincipal.Identity.Name property) does not have enough permissions, otherwise, we print a positive message indicating success. The logic for the Reports button is the same expept that the role used is different.

When the program is now executed, the following output will be observed for the user-name srinivas.

Note that I have captured the failure message. If you click on the Payroll button, you will get a success message. You can test the program using the other user name that we have hardcoded in the application.

That brings us to the end of this article. Just to summarize, we have seen how to use role-based security for Windows based users and custom users using the Windows* and Generic* objects. The implementation logic is quite simple and you can use it to provide useful functionality in your applications. The logic is the same for web-based applications except for certain differences. We will deal with implementing role-based security for web-based applications in a future article.

A good exercise for you to try would be to replace the hadrcoding in the application to database calls so that the example becomes more realistic. Have fun!!

Home