Role-Based Security checks in ASP.net

Post date: Aug 1, 2011 3:59:58 AM

Role-based security is not new to the .NET Framework. If you already have experience developing COM+ components, you surely have come across role-based access security. The concept of role-based security for COM+ applications is the same as for the .NET Framework. The difference lies in the way security is implemented.

When we talk about role-based security, we repeatedly use the same example. This is not because we can’t create our own example, but because it explains role-based security in a way everyone understands. So here it is: You build a financial application that can handle deposit transactions. The rule in most banks is that the teller is authorized to make transactions up to a certain amount—let’s say $5,000. If the transaction goes beyond that amount, the teller’s manager has to step in to perform the transaction. However, because the manager is only authorized to do transactions up to $10,000, the branch manager has to be called to process a deposit transaction that is over that amount.

Therefore, using this analogy, role-based security has to do with limiting the tasks a user can perform, based on the role(s) that user plays or the user’s identity. Within the .NET Framework, this all comes down to the principal that holds the identity and role(s) of the caller. As discussed earlier in this chapter, every thread is provided with a principal object. To have the .NET Framework handle the role-based security in the same manner as it does code access security, we define the permission class PrincipalPermission. To avoid any confusion, PrincipalPermission is not a derived class of CodeAccessPermission. In fact, PrincipalPermission holds only three attributes: User, Role, and the Boolean IsAuthenticated.

Principals

Let’s get back to where it all starts: the principal. From the moment an application domain is initialized, a default call context is created, to which the principal will be bound. If a new thread is activated, the call context and the principal are copied from the parent thread to the new thread. Together with the Principal object, the Identity object is also copied. If the CLR cannot determine the principal of a thread, a default Principal and Identity object is created so that the thread can run at least with a security context with minimum rights. There are three type of principals: WindowsPrincipal, GenericPrincipal, and CustomPrincipal. The latter goes beyond the scope of this appendix and is not discussed any further. Let’s take a look at the first two.

WindowsPrincipal

Because the WindowsPrincipal that references the WindowsIdentity is directly related to a Windows user, this type of identity can be regarded as very strong because an independent source authenticated this user.

To be able to perform role-based validations, you have to create a WindowsPrincipal object. In the case of the WindowsPrincipal, this process is reasonably straightforward, and there are actually two ways of implementing it. The one you choose depends on whether you have to perform just a single validation of the user and role(s), or you have to do this repeatedly. Let’s start with the single validation solution:

If you have to perform role-based validation repeatedly, binding the WindowsPrincipal to the thread is more efficient, so that the information is readily available. In the previous example, you did not bind the WindowsPrincipal to the thread because it was intended to be used only once. However, it is good practice to always bind the WindowsPrincipal to the thread; that way, in case a new thread is created, the principal is also copied to the new thread:

It is possible to bind the WindowsPrincipal in the first method of creation to the thread. However, your code must be granted the SecurityPermission permission to do so. If that is the case, you bind the principal to the thread with the following:

C#: Thread.CurrentPrincipal = WinPrinc; VB.NET: Thread.CurrentPrincipal = WinPrinc

GenericPrincipal

In a situation in which you do not want to rely on the Windows authentication but want the application to take care of it, you can use the GenericPrincipal.

Note 

Always use an authentication method before letting a user access your application. Authentication, in any shape or form, is the only way to establish an identity. Without it, you are not able to implement role-based security.

Let’s assume that your application requested a username and password from the user, checked it against the application’s own authentication database, and established the user’s identity. You then have to create the GenericPrincipal to be able to perform role-based verifications in your application:

Manipulating Identity

You can manipulate the identity that is held by a principal object in two ways. The first is replacing the principal; the second is by impersonating an identity.

Replacing the principal object on the thread is a typical action you perform in applications that have their own authentication methods. To be able to replace a principal, your code must have been granted the SecurityPermission, or more specifically, the SecurityPermission attribute ControlPrincipal. This will allow your own code to be able to pass on the PrincipalObject to other code. This attribute grants you the permission to manipulate the principal so that the CLR allows you to pass on the principal. You can replace the Principal object by performing these steps:

At the end of the impersonation, you have to change back to the original user account by calling the Undo method of the WindowsImpersonationContext.

Remember, the Principal object is not changed; rather, the WindowsIdentity token, representing the Windows account, is switched with the current token. At the end of the impersonation, the tokens are switched back again, as shown in the following steps:

You could have done Steps 2 and 3 in one statement that looks like this:

   Dim WinImpersCtct As WindowsImpersonationContext = _              WindowsIdentity.Impersonate(hImptoken)

Remember that you cannot impersonate when you use a GenericPrincipal, because it does not reference a Windows identity. For generic principals, you need to replace the principal with one that has a new identity.

Role-Based Security Checks

Now that we’ve discussed the creation and manipulation of PrincipalObject, it’s time to take a look at how it can assist you in performing role-based security checks. Here is where PrincipalPermission, already mentioned in the beginning of the “Role-Based Security” section, comes into play. Using PrincipalPermission, you can make checks on the active Principal object, whether WindowsPrincipal or GenericPrincipal. The active Principal object can be one you created to perform a one-time check, or it can be the principal you bound to the thread. Like the code access permissions, the PrincipalPermission can be used in both the declarative and the imperative way.

To use PrincipalPermission in a declarative manner, you need to use the PrincipalPermissionAttribute object, as shown in Figures A.17 and A.18.

[PrincipalPermissionAttribute(SecurityAction.Demand, Name = "User1", Role =  "Role1" )]  public int Act2()  {   return 1; } [assembly:PrincipalPermissionAttribute(SecurityAction.Demand, Role =  "Administrator")] 

Figure A.17: Using the PrincipalPermissionAttribute: C#

Public Shared Function _ <PrincipalPermissiobAttribute(SecurityAction.Demand, _      Name := "User1", Role := "Role1")> Act2() As Integer           ' body of the function End Function <assembly: PrincipalPermissionAttribute(SecurityAction.Demand, Role :=  'Administrator')> 

Figure A.18: Using the PrincipalPermissionAttribute: VB.NET

To use the imperative manner, you can perform the PrincipalPermission check, as shown in Figures A.19 and A.20.

PrincipalPermission PrincPerm = new PrincipalPermission("User1", "Role1"); PrincPerm.Demand(); 

Figure A.19: Using PrincipalPermission: C#

Dim PrincPerm As New PrincipalPermission("User1", "Role1") PrincPerm.Demand() 

Figure A.20: Using PrincipalPermission: VB.NET

It is also possible to use the imperative to set the PrincipalPermission object in two other ways, as shown in Figures A.21 and A.22.

PrincipalPermission PrincPerm = new  PrincipalPermission(PermissionState.Unrestricted);  

Figure A.21: C#

Dim PrincState As PermissionState = Unrestricted Dim PrincPerm As New PrincipalPermission(PrincState) 

Figure A.22: VB.NET

The permission state (PrincState) can be None or Unrestricted, where None means the principal is not authenticated. Therefore, the username is Nothing, the role is Nothing, and Authenticated is false. Unrestricted matches all other principals.

bool PrincAuthenticated = true; PrincipalPermission PrincPerm = new PrincipalPermission("User1", "Role1",  PrincAuthenticated); 

Figure A.23: C#

Dim PrincAuthenticated As Boolean = True Dim PrincPerm As New PrincipalPermission("User1", "Role1", PrincAuthenticated) 

Figure A.24: VB.NET

The IsAuthenticated field (Princauthenticated) can be true or false.

In a situation in which you want PrincipalPermission.Demand() to allow more than one user/role combination, you can perform a union of two PrincipalPermission objects. However, this is possible only if the objects are of the same type. Thus, if one PrincipalPermission object has set a user/role, and the other object uses PermissionState, the CLR throws an exception. The union looks like Figures A.25 and A.26.

PrincipalPermission PrincPerm1 = new PrincipalPermission("User1", "Role1"); PrincipalPermission PrincPerm2 = new PrincipalPermission("User2", "Role2"); PrincPerm1.Union(PrincPerm2).Demand(); 

Figure A.25: C#

Dim PrincPerm1 As New PrincipalPermission("User1", "Role1") Dim PrincPerm2 As New PrincipalPermission("User2", "Role2") PrincPerm1.Union(PrincPerm2).Demand() 

Figure A.26: VB.NET

The Demand will succeed only if the principal object has the user User1 in the role Role1 or User2 in the role Role2. Any other combination fails.

As we mentioned before, you can also directly access the Principal and Identity objects, thereby enabling you to perform your own security checks without using PrincipalPermission. Besides the fact that you can examine a little more information, this solution also prevents you from handling exceptions that can occur using PrincipalPermission. You can query the WindowsPrincipal in the same way the PrincipalPermission does this:

Additionally for PrincipalPermission, you can check the following WindowsIdentity properties: