Security Features in ASP.NET - Authentication Introduction Security is one of the primary concerns for both developers and application architects. As there are lots of different types of website with varying security needs, the developers need to know how the security works and choose the appropriate security model for different applications. Some websites collect no information from the users and publish the information that is available widely such as search engine. Meanwhile, there are other sites that may need to collect sensitive information from their users (e.g. credit card numbers and other personal information). These websites need much stronger security implementation to avoid malicious attacks from external entities. Difference of ASP and ASP.NET Security Flow The security flow for ASP.NET page request is different from the classic ASP security flow. In ASP, IIS impersonates the authenticated user by default, and in ASP.NET, the developer has more control over configuring security at different level [2] . ASP.NET Security Fundamental Operations Security in the context of ASP.NET application involves 3 fundamental operations [1,4,5] : Authentication: the process of validating the identity of a user to allow or deny a request [4,9,10] . This involves accepting credentials (e.g. username and password) from the users and validating it against a designated authority. After the identity is verified and validated, the user is considered to be legal and the resource request is fulfilled. Future request from the same user ideally are not subject to the authentication process until the user logs out of the web application. Authorization: the process of ensuring that users with valid identity are allowed to access specific resources. Impersonation: this process enables an application to ensure the identity of the user, and in turn make request to the other resources. Access to resources will be granted or
This document is posted to help you gain knowledge. Please leave a comment to let me know what you think about it! Share it to your friends and learn new things together.
Transcript
Security Features in ASP.NET - Authentication
Introduction
Security is one of the primary concerns for both developers and application architects. As there are lots of different types of website with varying security needs, the developers need to know how the security works and choose the appropriate security model for different applications. Some websites collect no information from the users and publish the information that is available widely such as search engine. Meanwhile, there are other sites that may need to collect sensitive information from their users (e.g. credit card numbers and other personal information). These websites need much stronger security implementation to avoid malicious attacks from external entities.
Difference of ASP and ASP.NET Security Flow
The security flow for ASP.NET page request is different from the classic ASP security flow. In ASP, IIS impersonates the authenticated user by default, and in ASP.NET, the developer has more control over configuring security at different level [2].
ASP.NET Security Fundamental Operations
Security in the context of ASP.NET application involves 3 fundamental operations [1,4,5]:
Authentication: the process of validating the identity of a user to allow or deny a request [4,9,10]. This involves accepting credentials (e.g. username and password) from the users and validating it against a designated authority. After the identity is verified and validated, the user is considered to be legal and the resource request is fulfilled. Future request from the same user ideally are not subject to the authentication process until the user logs out of the web application.
Authorization: the process of ensuring that users with valid identity are allowed to access specific resources.
Impersonation: this process enables an application to ensure the identity of the user, and in turn make request to the other resources. Access to resources will be granted or denied based on the identity that is being impersonated. In other words, impersonation enables a server process to run using the security credentials of the client [6,9]. Thus, the ASP.NET applications are capable to execute the identity of client on whose behalf they are operating.
Since there are a lot of ASP.NET security features to cover, this paper will only focus on authentication in ASP.NET.
Authentication in ASP.NET
Authentication is one of the foremost features of web application's security. In ASP.NET, authentication is done at two levels [2]. First, IIS* will perform the required authentication then send out the request to ASP.NET, as described in Figure 1. For ASP.NET application, the
underlying web server is IIS. Therefore, every ASP.NET application can continue to leverage the security options provided by IIS.
Source: MSDN LibraryFigure 1 - Security Flow of IIS and ASP.NET
When the user requests a specific resource on the system, that request will come to IIS. IIS authenticates the user requesting the resource and then hands off the request and the security token for the authenticating user to ASP.NET worker process. ASP.NET worker process will decide whether to impersonate the authenticated user supplied by IIS or not.
If impersonation is enabled in the configuration setting in Web.config file, then ASP.NET worker process impersonates the authenticated user. Otherwise, the thread will run under the ASP.NET worker process identity. After all, ASP.NET checks whether the authenticated user is authorized to access these resources. If they are allowed to, ASP.NET serves the request; otherwise it sends an "access-denied" error message back to the user.
ASP.NET provides built-in support for user authentication through several authentication providers [1,4]:
Forms-based authentication: the application is secured by using a custom authentication model with cookie support.
Passport authentication: the application is secured by using Microsoft® Passport authentication. Passport is a single sign-on technology developed by Microsoft for use on the web.
Windows authentication: the application is secured by using integrated windows authentication where access to a web application is allowed only to those users who are able to verify their windows credentials.
There are scenarios where some applications do not use the authentication at all or the developer may want to develop custom authentication code. In this case, ASP.NET can set the authentication mode to None. However, the topic is out of scope of this article since it will only cover the Forms-based, passport and windows authentications.
Forms-Based Authentication
Forms-based authentication is used to implement customized logic for authenticating users without having to worry about session management using cookie. It gives developer more access to specify which files on the site can be accessed and by whom, and allows identification of a login page [3,7].
This mechanism will automatically redirect the unauthenticated user to login page and ask them to provide proper credentials (e.g. username/password combination). If login is successful, ASP.NET then issues the cookie to the user and redirect them to specific resources that they originally requested. This cookie allows the user to revisit particular protected resources without having to repeatedly log in. The mechanism is shown as below:
Figure 2 - Form Authentication Flow
In figure above, the user requests the restricted resources first. This request will go to IIS first and the user is authenticated by IIS. If the anonymous access is enabled in IIS or the user is successfully authenticated, it will hand off the request to ASP.NET application. ASP.NET checks to see whether a valid authentication cookie is attached to the request. If it is, it means the user credentials has been previously authenticated. ASP.NET will then perform the authorization check. If the user is authorized to access those resources, the access will be granted. Otherwise, the "access-denied" message is sent.
If the request does not have any cookie attached, ASP.NET redirects the user to the login page and solicits the credentials then resubmits for authentication. The application code checks those credentials. If authenticated, ASP.NET will attach the authentication ticket in the form of cookie to the response. If failed, the user can be redirected back to the login page telling the user that the username/password is invalid.
Set Up Forms-Based Authentication
Generally, setting up the Forms-based authentication involves 4 steps [2]:
1. Enable anonymous access in IIS
This has to be done as most of the users are considered to be non-Windows users, so they can get through IIS to get to ASP.NET. ASP.NET will always allow anonymous access to the login page though.
2. Configure <authentication> section in Web.config file
Web.config file contains the information related to the level and type of authentication service that is provided for a web application. The Forms-based authentication is enabled for a web application by setting the authentication mode attribute to Forms [3,8]:
As shown by the code above, the name attribute is the name of HTTP cookie. The attribute loginURL is set to Login.aspx, which is the web page that is used for authenticating user credentials. The requests are redirected to particular URL in loginURL if the user is not authenticated.
The cookie protection is set to All. This causes the ASP.NET runtime to not only encrypt the cookie contents, but also validate the cookie contents. The valid values for protection attribute are All, None, Encryption, and Validation [9,10]. If the value is specified to None, it does not use either encryption or validation. Specifying Encryption will encrypt the cookie using triple DES or DES encryption algorithm; the data validation is not done on the cookie. The Validation specifies to validate that the cookie data has not been altered in the transit, instead of encrypting the contents of the cookie.
The timeout is set to 10, which means in 10 minutes the authentication cookie will expire. The idea behind this is to reduce the chance someone stealing the form authentication cookie. By reducing this, the cookie will be regenerated more often.
The path attribute refers to the path of cookie to be sent to the client. It is set to "/" which means the cookie path is the root directory.
The actual authentication (i.e. prompting the user to provide credentials) is performed by Login.aspx. The following code in Login.aspx passes the username and password that the user entered to the static System.Web.Security.FormsAuthentication method called Authenticate:
The code above will first check the username and password passed by the user, if they are valid, it will return true then go to the next statement. Next, it creates an authentication cookie, attaches it to the outgoing response and redirects user to original requested page. The second parameter specifies whether the authentication should be session cookie (false) or a persistent cookie (true) [7].
3. Configure <authorization> section in Web.config file
Add authorization support to section of ASP.NET web application. To do so, add the <authorization> section in Web.config file:
As explained above, after the user provides the valid credentials, the user is redirected to the specific protected page. However, The authorization section in this code will deny access to all users, but exclusively allow access to Cynthia.
In some cases, the following code is used to allow any authenticated user to access the protected resources [2]:
The "?" means all unauthenticated, anonymous user [5]. It will deny all unauthenticated or anonymous users.
One important thing to note that the code:
<deny users="*" /><allow users="Cynthia" />
is different from:
<allow users="Cynthia" /><deny users="*" />
since ASP.NET will stop at <deny users="*" /> and abandon the rest statements appear after that [7]. Therefore in the first one, it will deny ALL users instead of giving access to Cynthia only.
4. Create Login Page
This is the last step for redirecting unauthenticated users, so they can provider their credentials, usually in a form of username and password and log on to protected resources. The login page must validate the submitted credentials against a database of some custom method. Valid usernames and passwords can be stored in the Web.config file in credentials section:
However, storing password in clear text is unreasonable for security. Moreover, it is unrealistic to store thousands of names and passwords in Web.config file [2,7]. To address this problem, the usernames and passwords are stored in the database. This approach makes the Web.config file no longer have the <credentials> section. There will be also some changes in Login.aspx since the credentials will be tested to match against result query from database that stores the usernames and passwords.
In Login.aspx, instead of using FormsAuthentication.Authenticate to validate user credentials, it will call a local method (i.e. CheckValidity) which makes use a SQL query to determine whether the credentials are valid. The sample code as follows:
Bool CheckValidity(String username, String password){ SqlConnection conn = new SqlConnection ("server=localhost; database=weblogin; uid=sa; pwd="); try { conn.Open(); String sqlQuery = "select count (*) from users where username =\' " + username + "\' and password=\' " + password + "\' "; SqlCommand command = new SqlCommand(sqlQuery, conn);
int count = (int)command.ExecuteScalar(); return (count>0); }
This function will check for username and password given by the user to match against the database (i.e. users table) and it will return 0 if the credentials are not valid because there is no such record in the database, otherwise it will return 1, which means the credentials are valid.
Benefit of Forms-Based Authentication
There are some benefits of using Forms-based authentication [5]:
Developer can configure Forms-based authentication for various parts of the website differently because the Web.config is a hierarchical XML document.
Administrator and developer can change the authentication scheme quickly and easily in the Web.config file
Administration is centralized because all the authentication entries are in one place - Web.config file.
Passport Authentication
As stated above, this authentication mechanism provides a centralized authentication service that offers single sign-in for access the member sites. The following scenarios support the use of Passport Authentication [2]:
The username and password database or login page is not maintained; and Willing to provide personalized content; and The site will be used in conjunction with other Passport sites; and Willing to give single sign-in capability to the users
Set Up Passport Authentication
To implement this authentication mode, Passport SDK (Software Development Kit) has to be installed on the server and register with Microsoft® Passport [1,2]. The following code is specified in the Web.config file where the authentication mode is set to Passport:
The redirectURL attribute of Passport section is set to internal, which means the unauthenticated request will receive common error message. The value of redirectURL may contain a string other than internal, which is considered to be a URL, which the unauthenticated request will be sent to.
Windows Authentication
This type of authentication is possibly the easiest of all to implement. Windows authentication can be used in conjunction with almost all authentication methods provided by IIS (e.g. Basic, Digest, NTLM or Kerberos Authentication), except Anonymous Authentication [2,4]. There is no need to write any code to validate the user as IIS has already authenticated their Windows credentials. Basically, Windows authentication makes use of the authentication capabilities of IIS. IIS will complete its authentication first then ASP.NET will use the authenticated identity's token to decide whether the access is granted or denied.
This mechanism is usually implemented when the users are part of Windows domain and the authenticated users are to be impersonated so that the code is executed in the same security context of the user's Windows account [4].
When a user requests specific resources, this request will go to IIS. IIS authenticates the user and attaches the security token to it. It will then pass the authenticated request and security token to ASP.NET. If impersonation is enabled, ASP.NET impersonates the user using the security token attached and sees whether the user is authorized to access the resources in the <authorization> section in Web.config file. If the access is granted, ASP.NET will send the requested resources through IIS, or else, it sends error message to the user.
Set Up Windows Authentication
The only step in implementing the Windows Authentication is to set the authentication mode to Windows and deny access to anonymous user in Web.config file as shown below:
The impersonation is enabled only if the code is to be under same security context as that of the user account. Again, this is done in the configuration file.
Conclusion
Authentication in ASP.NET is one of the best features of the web application's security, which it is divided into 3 different built-in providers: Forms-based, Passport and Windows Authentication. The Forms-based and passport authentication do not require the users to be as Windows users. Meanwhile, the windows authentication is designed for users that are part of Windows domain. Forms-based authentication provides the unauthenticated users with the login page to ask them for their credentials, and it will validate those credentials against the designated authority. Once authenticated, the valid users will be granted to access the original requested resources. Future request from those users of the protected resources will automatically be redirected without having to repeatedly log in. On the other hand, if the users are not authorized to access specific resources, it will send the access-denied message back to the users. For Passport authentication, just simply install the Passport SDK on the server and register with Microsoftâ Passport. This mechanism offers a single sign-in provided by Microsoft to allow access to the member sites. Whereas, the Windows authentication is the easiest to implement as it does not require writing any code for authentication. It works in conjunction with IIS authentication mechanisms such as Basic, Digest, NTLM or Kerberos. However, it does not support for IIS Anonymous authentication.
ASP.NET Security [Part I]
Security is one of the most important concerns in application software development. Building a
robust security model is one of the most important factors that drive the success of application
software. As far as security in ASP.NET is concerned, three terms come into my mind, i.e.,
Authentication, Authorization and Impersonation. Put simply, authentication authenticates the
user’s credentials and authorization relates to the resources that an authenticated user has
access to. This article is the first in a series of articles on ASP.NET security and discusses these
concepts and their applicability.
Let us start our discussion with a brief outline on the sequence of events are as far as
authentication and authorization are concerned when a new request comes in. When a new
request arrives at IIS, it first checks the validity of the incoming request. If the authentication
mode is anonymous (default) then the request is authenticated automatically. But if the
authentication mode is overridden in the web.config file settings, IIS performs the specified
authentication check before the request is passed on to ASP.NET.
ASP.NET then checks whether Impersonation is enabled or not. We will discuss impersonation
later in this article. If impersonation is enabled, ASP.NET executes with the identity of the entity
on behalf of which it is performing the task; otherwise, the application executes with the
identity of the IIS local machine and the privileges of the ASP.NET user account. Finally, the
ASP.NET engine performs an authorization check on the resources requested by the
authenticated user and if the user is authorized, it returns the request through IIS pipeline.
The following section discusses Authentication, Authorization and Impersonation and how we
can implement them in ASP.NET applications.
Authentication
Authentication determines whether a user is valid or not based on the user’s credentials. Note
that a user can be authorized to access the resources provided the user is an authenticated user.
The application’s web.config file contains all of the configuration settings for an ASP.NET
application. An authentication provider is used to prove the identity of the users in a system.
There are three ways to authenticate a user in ASP.NET:
Forms authentication
Windows authentication
Passport authentication
Forms Authentication
This is based on cookies where the user name and the password are stored either in a text file or
a database. It supports both session and persistent cookies. After a user is authenticated, the
user’s credentials are stored in a cookie for use in that session. When the user has not logged in
and requests for a page that is secured, he or she is redirected to the login page of the
application. The following code snippet illustrates how this can be implemented in ASP.NET.
We have had a look at some of the most important concepts related to ASP.NET security. Stay
tuned for the other articles in this series that will discuss other aspects of ASP.NET security.
Implementing Role-Based Security with ASP.NET
Before we start, let's take a quick look at what roles are, and how they might be commonly used within an application. Imagine for a moment that you have been assigned the task of converting an online forum from Classic ASP to ASP.NET. One of the hurdles that you are likely to face lies in determining the identities of the users, and then granting access to certain features based on who that user is and what "role" they play within the application.
As you begin to form a picture of what roles are required, you might find that you end up with four basic "classes" of user:
1. System Administrator - this class of user can perform any task - add/delete posts and forums, ban users, approved messages posted to moderated forums, etc.
2. Moderator - this user can only approve messages that were posted to moderated forums.
3. Subscriber - these are users that are registered and authorized to post new messages, but are restricted from all administrative areas of the site.
4. Other/Public - these users merely have permission to browse to public pages within the site and read-only access to forum posts.
If you've come straight out of developing classic ASP applications, your first instinct may be to maintain user state in the Session object, and, then do some type of database lookup for an associated UserType to establish the role of the user. (In fact a previous 4Guys article, Logins and Permissions, demonstrates how to accomplish this "role"-based authorization using Session variables in classic ASP.)
' Only allow System Administrators and ModeratorsSelect Case userType
Case "1", "2" :' User is OK
Case Else :' Redirect to unauthorized access page
End Select
Typically, classic ASP applications relied heavily on this type of model, involving repeated database lookups to retrieve role based information.
With ASP.NET, however, this model changes. Before examining how roles are handled in ASP.NET, let's first translate the classic ASP Session-based code from above into VB.NET code that would appear in our ASP.NET Web page:
If User.IsInRole("SysAdmin") OrElse User.IsInRole("Moderator") Then' User is OK
Else' Redirect to unauthorized access page
End If
Although the above code probably doesn't mean much to you yet, it will by the time we've finished. Essentially, you can see that we interrogate a User object to extract the role information. But what is this User object?, And how does it get the role information?, I hear you asking.
Principals, Roles, and Identities, Oh My!If you've done any administering of Windows you will be familiar with the concept of Users and Groups. Basically, to access a secured network you must have a user account and that account would be assigned to one or many groups. In .NET these are referred to as Identities and Roles respectively, and they are contained within a Principal object. To understand how role based
authorization works you need to have an understanding of how these three elements are tied together, and how you can programmatically access their values. Let's look at each element separately.
IdentitiesIdentities represent users, and as such, have properties that allow you to obtain information (such as the username) about that user.
The classes for working with Identities reside in the System.Security.Principal Namespace. This namespace contains two classes: GenericIdentity and WindowsIdentity through which you can determine the properties of a user; and one interface: IIdentity that you can use to create custom Identities that can extend the base Identity type to suit needs that are specific to your application.
' Getting the username of the current userResponse.Write (User.Identity.Name) ROLESRoles are simply a comma-delimited String of role names that are added to the Principal to associate the current user with a role/s. ' Creating a string of rolesDim roleString() As String = {"manager", "cleaner"} PrincipalsA Principal contains information about the identity and role/s that the current user is associated with. It is through the Principal that you are able to check the role membership of the current user. In many ways a Principal is the glue that binds identities, roles, and the various other pieces of information that fully describe that Principal to the application.
A Principal is encapsulated by classes found in the System.Security.Principal Namespace. This namespace contains two Classes: GenericPrincipal and WindowsPrincipal through which you can determine the properties of a principal; and one interface IPrincipal that you can use to define your own custom Principals.
The .NET runtime uses the Principal that is attached to the current thread to gain information about the identity and roles of a user when handling requests that require authorization. To programmatically assign your own principal settings you simply create an instance of the Principal class passing in an identity object and a comma delimited string of roles for that identity. The constructor for the Principal object looks like this:
Public Sub New( _ ByVal identity As IIdentity, _ ByVal roles() As String _)
Therefore at the time of authentication you might do something like this to add an authenticated user to a principal, assign it some roles, and attach it to the current thread.
Creating your own roles
' Global.asax event handler that fires upon attempting to authenticate the userSub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs) If Request.IsAuthenticated() Then ' create an array of roles for the current user ' these would most likely be dynamically read ' from the data store for each user. Dim arrRoles() As String = {"Manager", "Cleaner"} ' Add our Principal to the current context Thread.CurrentPrincipal = New GenericPrincipal(Context.User.Identity, arrRoles) End IfEnd Sub
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 Application_AuthenticateRequest handler is the ideal place to do this.
Bringing it All TogetherI'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:
1. Validate the User - check the users credentials against a data store of credentials.2. Create an Identity
Dim objIdentity As GenericIdentity = New GenericIdentity("txtUsername") 3. 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())
4. Add the Identity and the Roles to a Principal Dim objPrincipal As GenericPrincipal = New GenericPrincipal(objIdentity, strRoles)
5. Add the Principal to the current context of the current thread
Thread.CurrentPrincipal = objPrincipal
Using RolesOnce 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:
ConfigurativelyYou can configure the authorization and/or the location elements within the Web.config configuration 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 ForumAdmin directory, 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.
ProgrammaticallyAlternatively, 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:
If Not (User.IsInRole("Public")) And Not (User.IsInRole("Other")) Then ' Display the linkElse ' Don't display it!End If
Or said in another manner:
If Not (Thread.CurrentPrincipal.IsInRole("Public")) _And Not (Thread.CurrentPrincipal.IsInRole("Other")) Then
' Display the linkElse ' Don't display it!End If ImperativelyYou can also imperatively demand that a user be in a role to access code by creating an instance of the PrincipalPermission class, configuring it with the user and role that you are checking for, then call the Demand method of that object to do the check. If the check fails, a SecurityException is raised. This is the previous example re-written to use imperative checking: Dim objPermission As New PrincipalPermission(User.Identity.Name, "manager")
Try objPermission.Demand() Catch ex As SecurityException ' Don't display it!End Try
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.
DeclarativelyAs 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: ' Create a method that disables all moderator permissions' and attach a PrincipalPermissionAttribute to it that issues' a Demand.
<PrincipalPermissionAttribute(SecurityAction.Demand, Name:="smith", Role:="SysAdmin")> _Public Sub DismissModerator() ' logic hereEnd Sub
And now, to call it:
Try DismissModerator()Catch ex As SecurityException ' do something elseEnd Try
What has happened here is that the Permission check that is bound to the DismissModerator() Sub 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.
SummaryIn 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.
Creating your own roles
' Global.asax event handler that fires upon attempting to authenticate the userSub Application_AuthenticateRequest(ByVal sender As Object, ByVal e As EventArgs) If Request.IsAuthenticated() Then ' create an array of roles for the current user ' these would most likely be dynamically read ' from the data store for each user. Dim arrRoles() As String = {"Manager", "Cleaner"} ' Add our Principal to the current context Thread.CurrentPrincipal = New GenericPrincipal(Context.User.Identity, arrRoles) End IfEnd Sub
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 Application_AuthenticateRequest handler is the ideal place to do this.
Bringing it All TogetherI'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:
1. Validate the User - check the users credentials against a data store of credentials.2. Create an Identity
Dim objIdentity As GenericIdentity = New GenericIdentity("txtUsername") 3. 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())
4. Add the Identity and the Roles to a Principal Dim objPrincipal As GenericPrincipal = New GenericPrincipal(objIdentity, strRoles)
5. Add the Principal to the current context of the current thread
Thread.CurrentPrincipal = objPrincipal
Using RolesOnce 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:
ConfigurativelyYou can configure the authorization and/or the location elements within the Web.config
configuration 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 ForumAdmin directory, 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.
ProgrammaticallyAlternatively, 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:
If Not (User.IsInRole("Public")) And Not (User.IsInRole("Other")) Then ' Display the linkElse ' Don't display it!End If
Or said in another manner:
If Not (Thread.CurrentPrincipal.IsInRole("Public")) _And Not (Thread.CurrentPrincipal.IsInRole("Other")) Then
' Display the linkElse ' Don't display it!End If ImperativelyYou can also imperatively demand that a user be in a role to access code by creating an instance of the PrincipalPermission class, configuring it with the user and role that you are checking for, then call the Demand method of that object to do the check. If the check fails, a SecurityException is raised. This is the previous example re-written to use imperative checking: Dim objPermission As New PrincipalPermission(User.Identity.Name, "manager")
Try objPermission.Demand() Catch ex As SecurityException ' Don't display it!End Try
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.
DeclarativelyAs 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: ' Create a method that disables all moderator permissions' and attach a PrincipalPermissionAttribute to it that issues' a Demand.
<PrincipalPermissionAttribute(SecurityAction.Demand, Name:="smith", Role:="SysAdmin")> _Public Sub DismissModerator() ' logic hereEnd Sub
And now, to call it:
Try DismissModerator()Catch ex As SecurityException ' do something else
End Try
What has happened here is that the Permission check that is bound to the DismissModerator() Sub 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.
SummaryIn 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.
ASP.NET 2.0
The Provider Model
If you've downloaded and used the ASP.NET Starter Kits available from www.asp.net,
the Provider Model and its implementation probably won't have come as too much of a
shock. Microsoft began to employ the "Provider Design Pattern" in its Starter Kits in the
summer of 2002, but didn't officially formalize it as a model until the development of
ASP.NET 2.0's Personalization feature.
Without boring you with the specifics, let me generalize by saying that because of the
Provider Model, security in ASP.NET 2.0 is much more simplistic, easy to configure, and
quick to implement than it was previously. The model assumes the configuration of
users and roles by automatically creating a database within SQL Server (or, if you choose
to use Access, as an .MDB file within the Data folder of your application). Immediately,
you're able to start to register and authenticate users against this newly created table.
Gone are the days of having to build the database schema by hand, and then having to
tailor each Web application to work in its respective database schema.