Implementing Role-Based Security with ASP.NET, Part 2By Darren Neimke
In Part 1 we examined what role-based security is from a high-level. Furthermore, we looked at the three important sections involved in such a security scheme: Identities, Roles, and Principals. In this part we'll look at how to create our own roles.
Creating your own roles
This only needs to be done once because the runtime automatically copies a reference to the principal
object from the calling thread to the
CallContext of the new thread.
If you cast your mind back to how FormsAuthentication works, you will remember that a user is authenticated and then an authentication cookie attached to the validating Response when you call one of the appropriate static methods of the FormsAuthentication provider. It is at that moment that the
Application_AuthenticateRequest handler is called.
When authenticating users you do not have to do anything in this handler, nor do you need to explicitly create
a new instance of the
GenericPrincipal class. However, to dynamically assign custom roles to
a user (which is what we are interested in today) you do need to, and the
handler is the ideal place to do this.
Bringing it All Together
I'll stop here briefly, before we move onto the really cool stuff, to summarize the five steps needed to allow your application to implement role based checking:
- Validate the User - check the users credentials against a data store of credentials.
- Create an Identity
Dim objIdentity As GenericIdentity = New GenericIdentity("txtUsername")
- Get the roles for the current user
Dim strRoles() As String Dim arrRoles As New ArrayList() ' do some database call that returns a reader While reader.Read() arrRoles.Add(reader("role")) End While strRoles = arrRoles.ToArray(GetType(String), String())
- Add the Identity and the Roles to a Principal
Dim objPrincipal As GenericPrincipal = New GenericPrincipal(objIdentity, strRoles)
- Add the Principal to the current context of the current thread
Thread.CurrentPrincipal = objPrincipal
Once you have authenticated a user and defined their identity and roles there are four ways that you can interact with the Principal to enforce permissions based on memberships within the application:
You can configure the authorization and/or the location elements within the
Web.configconfiguration files to grant or deny access to entire areas within the application outright, as opposed to the other 3 methods which suit a more piecemeal approach.
For example, in the application that we are developing it may be necessary to ensure that only users acting as System Administrators or Moderators are allowed access to certain administrative screens.
To demonstrate this, let's presume that all of the forms that allow users to moderate forum posts reside under a directory called "ForumAdmin". Rather than programatically checking the role of a user inside every file that sits in that directory, you can simply configure the
Web.config file for that folder to do the checking for you.
The following snippet excludes all users except for System Administrators or Moderators from accessing files in the directory that the
Web.config file resides in (as well as its subdirectories):
By placing the abovementioned
Web.config at the root of the
the application itself will inspect the roles of the user making the Request to determine whether access is
granted. That's right, no additional code is neccessary, regardless of how many
.aspx files are
placed under that folder. (Compare that to classic ASP and the Session-based approach, where every "sensitive"
page needs to have a check at the top of the page to determine if the user has rights to view the page...)
Even from this simple example, you should get an idea of how this new approach simplifies the task of authorization, and, how it supersedes the methods available under the current practices of classic ASP.
Alternatively, you may have directories that contain files that will be accessed by users acting in a wide variety of roles. An example of this would be the directory that displays the actual forum itself. You need to allow viewing access to all areas of the forum, even to unauthenticated members of the public, but to create a new post, it is imperative that a user not be in the role of Public or Other.
In your code, whenever you need to check to see if the user is in a role you simply query the
IsInRole method of the current user.
Let's look at the code snippet responsible for displaying the link to create a new forum post:
Or said in another manner:
You can also imperatively demand that a user be in a role to access code by creating an instance of the
PrincipalPermissionclass, configuring it with the user and role that you are checking for, then call the
Demandmethod of that object to do the check. If the check fails, a
SecurityExceptionis raised. This is the previous example re-written to use imperative checking:
The downside to the two previous methods is that if you are calling a method several times from different parts of the application, you need to repeat this logic all over the place.
As many of you would know, one of the great benefits of using Stored Procedures in the database is that you can enforce permissions specific to an individual stored procedure. With declarative checks you can bind permissions to an actual method (or event, or whatever) using meta-data attached to each specific object that requires it. For our purposes, let us assume that we have a method called
dismissModerator(). Now, for obvious reasons, we do not want the moderator, or anyone else for that matter, to be able to call this method! Therefore, only users who are in the role of System Administrator can access this method. Let's set it up:
And now, to call it:
What has happened here is that the Permission check that is bound to the
is carried out before execution of the Sub takes place. As you can see, this method of authorization is good,
because you are enforcing the policy on an actual object, meaning that a developer cannot accidentaly call
the method and inadvertently dismiss the poor forum moderator, because the object itself does the role checking.
You can enforce declarative checking at class, class member, property or even event level. If you define a
permission attribute on a class as well as one of its members the declaration at member level overrides the
declaration at class level.
In this article we've had a brief look at what role based security is and how the .NET runtime can assist in implementing it in our solutions. If you found this article interesting I'd encourage you to delve further into the
System.Security.Permissions namespace where you'll find even more classes to assist you in
producing a customized solution. Pay particular attention to the
Union method of the
PrincipalPermission class which allows you to bind multiple principal permissions to create
a new permission.