N-Layered Web Applications with ASP.NET 3.5 Part 6: Security
NOTE: the concepts presented in this article are now considered obsolete possibly because better alternatives are available.
Note: this is the last part in a series of six. If you rather read this entire series off-line, you can buy the series as a convenient PDF document that comes with the full source. Besides the convenience, buying the PDF will also make you feel good as it shows your appreciation for the articles and helps me pay the bills for my server and hosting so I can keep running imar.spaanjaars.com and continue to provide you with great content. For more details, check out this post that shows you how you can buy the entire series right now.
This is part 6 of a six-part series of articles on N-Layer design. This article series builds on top of my three part series on N-Layer design that I released in early 2007. If you haven’t already done so, be sure to check out these articles first, as a lot of code and concepts used in this new series is explained in detail in the older series.
In this last article in the series, I'll deal with security in your N-Layered web application.
After you’ve read the previous series, be sure to check out part one as well as it describes the new application’s architecture. Additionally, you may want to read the earlier parts that deal with Validation, Sorting, Paging, Filtering and concurrency.
Quick Links to All Articles
Previous Series
Building Layered Web Applications with Microsoft ASP.NET 2.0 - Part 1
Building Layered Web Applications with Microsoft ASP.NET 2.0 - Part 2
Building Layered Web Applications with Microsoft ASP.NET 2.0 - Part 3
Custom Sorting with N-Layer Design Classes and the GridView
Current Series
N-Layered Web Applications with ASP.NET 3.5 Part 1: General Introduction
N-Layered Web Applications with ASP.NET 3.5 Part 2: Introducing the Validation Framework
N-Layered Web Applications with ASP.NET 3.5 Part 3: Advanced Validation Topics
N-Layered Web Applications with ASP.NET 3.5 Part 4: Sorting, Paging and Filtering
N-Layered Web Applications with ASP.NET 3.5 Part 5: Dealing with Concurrency
N-Layered Web Applications with ASP.NET 3.5 Part 6: Security
Security by itself is a major topic on which many books have already been written. This article is not intended to show you all the ins and outs, or do's and don’ts on security, but instead will focus on a few key areas. In particular, I’ll look at the following:
- Database security
- URL authorization in ASP.NET
- Declarative role based security using ASP.NET Membership and the RoleManager
- Imperative role based security using ASP.NET Membership and the RoleManager
Being an article series on N-Layer design, I’ll touch upon security in all three layers: the UI, the Business Layer and the database. My main focus will be on the business layer though. Many articles have already been written on database security and on ASP.NET URL authorization (where access to the UI is controlled by ASP.NET). Instead, I’ll refer to relevant articles and books where appropriate.
Database Security
Database security is a powerful concept. All modern database systems have security deeply integrated giving you fine control over who can access your system and who can’t. For example, Microsoft SQL Server allows you to control who can access a database, what tables can be read or modified, what stored procedures a user can access and so on. This is a very granular system that even allows you to configure security at the column level of a table (although you usually don’t want to go that route, and use views or stored procedures instead).
This model of security works really well when you know who your users are. For example, consider a desktop application that runs inside an Active Directory domain. Only users with an AD account can access the application and its underlying database. That in turn means you can configure users (or groups) to access specific parts (tables, schemas, stored procedures and so on) to your database.
This, however, turns out to be problematic in a web application. Who are your users in an ASP.NET web site? If you have a popular site, you may have thousands or even hundreds of thousands of users accessing your application. Clearly, it’s not an option to give all of them an Active Directory account to access the database. Instead, you typically configure your ASP.NET web site to run under a specific user account and grant access to that account. On most versions of IIS / ASP.NET, this account is called Network Service (Windows Vista, Server 2003 and 2008) although you’ll also find the ASPNET account.
One problem with this strategy is that the configured account needs the combined permissions of all the users in your system. So, if an anonymous visitor is only allowed to read from the News table (through the web application) but an Editor also needs to be able to create new or change existing news articles, it means that the account used by the web server now needs to be able to read from and write to the News table. So, how do you block your random visitors from messing with your News table? The answer is: URL authorization and Role Based Security in your Business Layer. These mechanisms allow you to block access to the functionality that is able to change the database. If a user is not able to access an ASPX file that writes to the database, you effectively took away the option to alter this table altogether.
I’ll briefly dig into URL Authorization first, followed by a deeper look at Role Based Security.
References
For more information about configuring SQL Server to work with ASP.NET 2.0 and 3.5 applications, check out the following article:
URL Authorization in ASP.NET
URL authorization is probably one of the easiest to implement security mechanisms for ASP.NET web sites. It’s pretty easy to configure (using tools like the Web Site Administration Tool that comes with Visual Web Developer), very visible (you can see the rules that apply in various .config files in your site) and it’s pretty secure as it ships out of the box with ASP.NET and as such requires no manual code from you, other than some initial configuration.
To implement URL authorization, you typically perform the following steps:
- Enable ASP.NET Membership and optionally the RoleManager functionality in the web.config file.
- Create at least one user and optionally one or more roles.
- Control access to one or more files in one or more web.config files.
- Provide a means for your users to log in.
Enable ASP.NET Membership and the RoleManager
By default, ASP.NET Membership is already activated. All you need to do to get started with ASP.NET is drag one of the Login controls like the Login box or the CreateUserWizard control on a page, view the page in a browser and try to login in. As soon as you do this, ASP.NET will create a new database for you in the App_Data folder called aspnetdb.mdf which it then configures for Membership. Any new accounts you create using the CreateUserWizard control are stored in the database automatically. Login attempts using the Login control are also executed against this database.
In order to use roles in your application, you need to enable the <roleManager /> by setting its enabled attribute to true.
If you want a bit more control over the various settings of the Membership and Role providers, you need to reconfigure the <membership /> and <roleManager /> elements in web.config. That way, you can override all the configuration settings that are defined by default. The following code snippet shows a typical configuration for these two providers with slightly more relaxed settings than the default (for example, the password requirements have been changed to accept passwords with a minimum length of 5):
<membership defaultProvider="AspNetSqlMembershipProvider"> <providers> <clear /> <add name="AspNetSqlMembershipProvider" type="System.Web.Security.SqlMembershipProvider, System.Web, Version=2.0.3600.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a" connectionStringName="LocalSqlServer" applicationName="/" requiresUniqueEmail="true" passwordFormat="Hashed" enablePasswordRetrieval="false" enablePasswordReset="true" requiresQuestionAndAnswer="false" passwordStrengthRegularExpression="" minRequiredPasswordLength="5" minRequiredNonalphanumericCharacters="0" passwordAttemptWindow="10" maxInvalidPasswordAttempts="5" description="Stores and retrieves membership data from a local Microsoft SQL Server database." /> </providers> </membership> <roleManager defaultProvider="SqlProvider" enabled="true" cacheRolesInCookie="true" cookieName=".ASPROLES" cookieTimeout="30" cookiePath="/" cookieRequireSSL="false" cookieSlidingExpiration="true" cookieProtection="All" > <providers> <add name="SqlProvider" type="System.Web.Security.SqlRoleProvider" connectionStringName="LocalSqlServer" applicationName="/" /> </providers> </roleManager>
Once you have the database and configuration in place you’re ready for the next step: creating users and roles inside your database.
If you want to combine your own database tables with those of ASP.NET in a single database, follow these steps:
-
Optionally, attach your database to SQL Server using SQL Server Management Studio. This allows you to use the GUI version of the aspnet_regsql tool.
-
Run the tool aspnet_regsql from your .NET Framework folder (located at %windir%\Microsoft.NET\Framework\v2.0.50727) against your database. This adds support for ASP.NET features like Membership, Roles and Profile.
If you want to modify a database you have not attached (for example, a database in the App_Data folder of your web site), execute the tool at the command line like this:aspnet_regsql -A all -C "Data Source=.\SqlExpress;Integrated Security=True;User Instance=True" -d "C:\Projects\Spaanjaars.ContactManager.Web\App_Data\aspnetdb.mdf"
-
Update the <providers> elements of the configuration for the Membership, Roles and Profile in web.config. You'll need to set the connectionStringName attribute to the name of a connection string in the same web.config that references the database you modified in step two.
- Optionally, detach the database from SQL Server again (this is necessary if you use AttachDBFileName attribute in the query string that is used to attach database on the fly.)
References
For more information on security and the aspnet_regsql tool, check out the following references:
- Creating the Application Services Database for SQL Server
- How to add a Login, Roles and Profile system to an ASP.NET 2.0 app in only 24 lines of code
- Configuring ASP.NET 2.0 Application Services to use SQL Server 2000 or SQL Server 2005
- Using ASPNET_RegSQL.exe with SQL Express databases in App_Data
- Aspnet_regsql with SQLExpress database
Creating Users and Roles using the WSAT or the CreateUserWizard
When you’re in Visual Web Developer, adding new users and roles is very simple. Choose WebSite | ASP.NET Configuration from the main menu to open the Website Administration Tool (the WSAT). From there you can configure security, add roles and new users and then assign your users to the various roles.
Alternatively, you can let new users sign up for an account using the CreateUserWizard control, you can create them programmatically (in your Global.asax for example) and you can manage them using the new IIS manager available on Windows Vista, Windows Server 2008 and later versions of Windows).
References
For more information, check out the following articles:
- How to add a Login, Roles and Profile system to an ASP.NET 2.0 app in only 24 lines of code
- Creating ASP.NET Users Programmatically
Control Access to Files in Web.Config
To control access to specific files or folders you can use <location /> elements in the central web.config file or use separate web.config files in sub folder and then use the <authorization /> element. I prefer to centralize stuff in the main web.config file so I typically have rules like this to block access to specific folders for all users except for one or more roles:
<location path="Management"> <system.web> <authorization> <allow roles="Administrators"/> <deny users="*"/> </authorization> </system.web> </location>
This blocks access to the Management folder for all users except for those in the Administrators role.
Allow Users to Login
Once you have Membership and Roles setup, the rest is pretty easy. In the most basic form, all you really need to allow a user to login is this code in a page:
<asp:Login id=”Login1” runat=”server” />
With this control in a page, your users will get a Login control with a user name and password field that allows them to sign in. Based on the roles they have been assigned to, they get access to specific areas of the applications. For example, if you’d log in as an Editor, you still wouldn’t be able to access the /Management folder as it’s only accessible to users in the Administrators role.
For many applications, URL authorization is the way to go. It’s easy to implement and very powerful. IIS 7 and later (available for Windows Vista and Server 2008 at the time of writing) make the concept even more powerful by applying the validation rules to content other than “registered ASP.NET files”, including images, documents and even PHP and ASP files, allowing you to use features like Membership and the RoleManager for these types of content as well.
However, just like database security, this is pretty much an all or nothing solution. You either get access to a specific file or folder, or you don’t. But what if you want adapt your pages to the user who’s logged in? What if you want to remove the Edit button for users who are not in the Editors role? Or what if you want to ensure only Administrators are able to call the Delete method on your AddressManager class? The answer is declarative and imperative Role Based Security, the topics of the next two sections.
References
For more information on integrating ASP.NET and IIS 7, check out the following references:
- http://msdn.microsoft.com/en-us/magazine/cc135973.aspx
- http://learn.iis.net/page.aspx/243/aspnet-integration-with-iis7/
Declarative Role Based Security
With declarative role based security, you apply attributes to methods or classes that restrict access to a limited set of users or roles. This allows you to stop users from calling these methods, or accessing or constructing instances of a class. Consider the following example:
[PrincipalPermission(SecurityAction.Demand, Role = "Administrators")] public class Person { }
By applying the PrincipalPermission attribute to the Person class, only administrators are allowed to use this class. If you’re not an administrator, you can’t create a new instance or access any of its shared methods.
While blocking access to the entire class might be useful in some cases, a more common scenario is to block access to certain methods instead. For example, anonymous readers might be allowed to read a specific item, but in order to Insert, Update or Delete an item you have to be an authenticated user. You could rewrite the Manager classes to support this as follows:
using System.Security.Permissions; using Spaanjaars.ContactManager.BusinessEntities; public static class NewAddressManager { public static AddressCollection GetList(int contactPersonId) { … } public static Address GetItem(int id) { … } [PrincipalPermission(SecurityAction.Demand, Authenticated = true)] public static int Save(Address myAddress) { … } [PrincipalPermission(SecurityAction.Demand, Authenticated = true)] public static bool Delete(Address myAddress) { } }
By setting the named parameter Authenticated to true, only logged in users can call these methods. The GetItem and GetList methods have no permission attributes applied and as such can be called by anyone.
Declarative role based security is a powerful concept and pretty easy to apply. All you need to do is add a few attributes to your methods and classes, set some of the parameters, like the users or roles that are allowed to access your code and you’re done. On top of that, it’s a .NET feature, not just an ASP.NET feature. That means you can use the same principles and knowledge in other .NET applications like Windows Forms and Console Applications to enforce security. It also nicely integrates with the standard ASP.NET Membership and Roles features and as such is thus not limited to Windows accounts only. The examples above work out-of-the-box with the standard ASP.NET Membership authentication mechanisms.
Simple and secure as declarative role based security may be, it has one big drawback: a run-time exception is thrown when an unauthorized user is trying to access the protected code. This could have some severe implications for the calling code you write as you’ll need to add a lot of try/catch statements to intercept the exception message. So usually I implement declarative role based security as a last line of defense to stop unauthorized code from calling methods that it’s not allowed to call. This is particularly useful in middle layer code, like the code in the Business and Data Access Layers. For example, when code in those layers is used in a different application – say a Win Forms application – and the developer using the code doesn’t know about the security restrictions, he’ll find out pretty soon what the code allows you to do and what not. This way the business layer code is safe from abuse and it’s up to the developer of the UI to implement the correct authorization mechanisms.
Besides declarative role based security that blocks entire classes and methods, .NET also supports imperative role based security that enables you to secure only parts of your method’s code. After I have discussed imperative role based security in the next section, I’ll show you how to use the ASP.NET Membership and RoleManager features in your ASPX pages to adapt the UI to the user who is currently logged on.
References
Check out the following references to learn more about role based security:
Imperative Role Based Security
Imperative role based security allows you to overcome the problem with the run-time exceptions that are thrown by the declarative model. Rather than blocking entire methods, you can protect specific portions of a method and then wrap the code in a try/catch block to catch any security exceptions. This in turn allows you to choose how to handle the exception. You can wrap the caught exception in one of your own exceptions and forward it to the calling code for example. Or you simply ignore the exception and refuse to perform the requested operation, return null or another value indicating the method didn’t do what it was supposed to do. Finally, it allows you log the exceptions so you can keep track of what attempts have been made to bypass your security.
To implement imperative role based security, you can create an instance of the PrincipalPermission class and then call its Demand method. The following example shows you how to stop users from calling the Delete method in the AddressManager class if they are not an Administrator:
public static bool Delete(Address myAddress) { try { PrincipalPermission myPrincipalPermission = new PrincipalPermission(null, "Administrators"); myPrincipalPermission.Demand(); return AddressDB.Delete(myAddress.Id); } catch (SecurityException) { // Log error here throw; } return AddressDB.Delete(myAddress.Id); }
When Demand is called, .NET checks the permissions for the current user. If the user is an Administrator, all is fine and the code continues to call the Delete method on the AddressDB class. But if the user is not an Administrator, Demand raises a SecurityException, effectively stopping the code from calling Delete.
This current implementation has the same effect as its declarative counterpart: it throws a run-time error when the user is not an Administrator. However, because you can wrap the call to Demand in a try/catch block, you can handle the error in any way you see fit.
Besides the Demand method of the PrincipalPermission class has a few other useful methods related to enforcing security. For example, the Intersect and Union methods enable you to create more complex permissions rules where a user needs to match one or more security rules. For more information, check out the links in the following references section:
References
For more information on the PrincipalPermission class, its methods and its underlying interface, check out the following resources:
- The PrincipalPermission class
- The Demand method
- The IPrincipal interface (the underlying interface for many security concepts)
While declarative and imperative role based security are very useful, they are not always enough to create a secure and usable application. Since they block access to code in such a abrupt way, you need other measures to prevent this code from being called by an unauthorized user in the first place.
Fortunately, this is very easy to do with the ASP.NET Membership and RoleManager features as you’ll see next.
Creating Adaptive UI’s with Membership and RoleManager - Security in the Contact Manager Application
Consider for a moment the Contact Manager Application in the context of security. What would you allow your users to do? The first thing to consider is your user base. Are users from the internet accessing your site? Or does the site run in a controlled environment like a company’s intranet? Depending on the target audience you may need to be more restrictive when it comes to implementing security.
Let’s say that the Contact Manager Application is used in an intranet application or a controlled public internet web site to share information about your contacts throughout your organization. Let’s also say you want to implement the following security features:
- Regular (e.g. non authenticated) users can view the contact details, but they are not allowed to add new or change existing records.
- Users in the groups Editors and Administrators can add new and edit existing contact people and alter their contact information.
- Only Administrators are able to delete contact people from the system.
Sounds like a lot of work? Not so with the ASP.NET membership and role features.
The first thing you need to do is block access to the AddEditContactPerson.aspx page in the root of the site to unauthenticated users. This is easy with .NET’s URL authorization. All you need to do is add a <location /> element to your web.config file like this:
<location path="AddEditContactPerson.aspx"> <system.web> <authorization> <deny users="?"/> </authorization> </system.web> </location>
As soon as an unauthenticated users (a user who hasn’t logged in to the system yet) tries to access this URL, she’s taken to the Login.aspx where she’s asked for her credentials.
Note: Rather than specifying a gazillion <location /> elements for each and every page in your application, it’s a good practice to combine related pages in one or more directories, and configure access for those directories instead.
The next step is hiding the Create new Contact Person button that appears at the top of the Default.aspx page. You can do this with a single line of code in the Page_Load event handler in the Code Behind of Default.aspx:
protected void Page_Load(object sender, EventArgs e) { btnNew.Visible = Membership.GetUser() != null; }
The GetUser method without arguments of the Membership class returns null when the current request is not associated with a logged in user. So the expression Membership.GetUser() != null returns false for non authenticated users and thus the button btnNew is hidden. Once you’re logged in, GetUser() returns a valid user and the button is visible again.
To comply with the second and the last requirement (only Editors and Administrators can edit and only Administrators can delete records) you need to hook into the RowCreated event of the GridView, find the Edit and Delete LinkButton controls in the GridView and hide or show them depending on the user’s group membership. To make it easy to find the LinkButton controls, I converted both columns in the GridView for editing and deleting to a TemplateColumn. By doing that, you can give each LinkButton a unique name, making it easier to find the control in Code Behind. The two columns for the LinkButton controls now look like this:
<asp:TemplateField ShowHeader="False"> <ItemTemplate> <asp:LinkButton ID="lnkEdit" runat="server" CausesValidation="false" CommandName="Edit" Text="Edit"></asp:LinkButton> </ItemTemplate> </asp:TemplateField> <asp:TemplateField ShowHeader="False"> <ItemTemplate> <asp:LinkButton ID="lnkDelete" runat="server" CausesValidation="False" CommandName="Delete" Text="Delete" OnClientClick="return confirm('Are you sure you want to delete this contact person?');"> </asp:LinkButton> </ItemTemplate> </asp:TemplateField>
With this code, it’s now easy to find the LinkButton controls and determine whether they should be visible or not:
protected void gvContactPersons_RowCreated(object sender, GridViewRowEventArgs e) { if (e.Row.RowType == DataControlRowType.DataRow) { bool userIsAdmin = Roles.IsUserInRole("Administrators"); bool userIsEditor = Roles.IsUserInRole("Editors"); LinkButton editButton = e.Row.FindControl("lnkEdit") as LinkButton; if (editButton != null) { editButton.Visible = userIsAdmin || userIsEditor; } LinkButton deleteButton = e.Row.FindControl("lnkDelete") as LinkButton; if (deleteButton != null) { deleteButton.Visible = userIsAdmin; } } }
This code first checks whether the current row is a DataRow (and not a Header or a Footer for example). It then determines whether the current user is an Administrator and / or an Editor. It does this by calling Roles.IsUserInRole and passing in the name of the role (note: to make it easier to maintain this application, you shouldn’t use hard coded values like this; instead you could define the role names in a file with constants, or add them to the application’s configuration file). The Roles class is part of the ASP.NET RoleManager and provides static methods like IsUserInRole to determine the role membership of the current user. Naturally, when the user is not even logged in, this method returns false. Otherwise, it checks whether the user is assigned to the role specified.
The final part of the code finds the Edit and Delete LinkButton controls and then determines their visibility based in the user’s group membership.
Security in the Contact Manager Application
I put most of the security implementation in the Contact Manager Application for demo purposes only. If you look at the application that comes with the article, you’ll find security in the classes and ASPX files as discussed in this article. If you want to see the sample code at work, check out the following files and classes:
- The ContactPersonManager class in the Spaanjaars.ContactManager.Bll project that uses PrincipalPermission.Demand to stop non administrators from deleting contact people.
- Default.aspx in the web application where the Create new Contact Person button is hidden in Page_Load.
- Default.aspx where the Edit and Delete links are hidden during the RowCreated event of the rows in the GridView that displays the contact people.
Of course you can use the exact same principles again to secure other parts of your application, including creating new contact data, deleting addresses and so on.
Summary and Wrapping Up
Security in Web Applications is a very large topic. What I have shown you is just the beginning. There’s a lot more that you need to be aware of in order to create secure and yet accessible applications.
To learn more about security in your ASP.NET Applications, consider getting a copy of the following books:
- Developing More-Secure Microsoft® ASP.NET 2.0 Applications
- Professional ASP.NET 2.0 Security, Membership, and Role Management
- Building Secure ASP.NET Applications: Authentication, Authorization, and Secure Communication (PDF)
Additionally, if you’re new to security in ASP.NET in general, and need to read up on the security features of ASP.NET including the controls like Login, CreateUserWizard, ChangePassword and so on, pick up a copy of my book Beginning ASP.NET 3.5 in C# and VB.NET and check out chapters 15 and 16.
With security, you have also come to the end of the article series on N-Layer design. Over the past six articles I covered a lot of different topics regarding N-Layer design, including:
- The modified architecture based on my V1 article series.
- The new project layout using multiple class library projects which is now a lot easier to implement since Visual Web Developer supports class library projects.
- The Validation Framework including the base classes that your entities can inherit from and the validation attributes that you can write yourself to enhance the set of validation rules.
- Sorting, paging and filtering at different levels of the application: in the UI, the Business Layer and at the database. You’ve seen how to implement these concepts in the Business Layer and at the database and you’ve seen a discussion of advantages and disadvantages of each.
- In part five of this series I showed you how to deal with concurrency issues to avoid two or more users from updating the same data at the same time.
- The series closed with an introduction to security in ASP.NET: you saw how to implement URL authorization and how to implement declarative and imperative role based security. These concepts allow you to create a fine-grained, flexible and yet very secure web site where you control who can access what.
Just as with the previous article series, I have covered a lot of topics. However, I also skipped many other topics that might be relevant in the scope of this N-Layer design article series. Feel something important is missing? Have an idea for a change in implementation? Make yourself heard by using the Talk Back feature below.
Downloads
Where to Next?
Wonder where to go next? You can post a comment on this article.
Links in this Document
Doc ID | 481 |
Full URL | https://imar.spaanjaars.com/481/n-layered-web-applications-with-aspnet-35-part-6-security |
Short cut | https://imar.spaanjaars.com/481/ |
Written by | Imar Spaanjaars |
Date Posted | 03/09/2009 10:33 |
Comments
Talk Back! Comment on Imar.Spaanjaars.Com
I am interested in what you have to say about this article. Feel free to post any comments, remarks or questions you may have about this article. The Talk Back feature is not meant for technical questions that are not directly related to this article. So, a post like "Hey, can you tell me how I can upload files to a MySQL database in PHP?" is likely to be removed. Also spam and unrealistic job offers will be deleted immediately.
When you post a comment, you have to provide your name and the comment. Your e-mail address is optional and you only need to provide it if you want me to contact you. It will not be displayed along with your comment. I got sick and tired of the comment spam I was receiving, so I have protected this page with a simple calculation exercise. This means that if you want to leave a comment, you'll need to complete the calculation before you hit the Post Comment button.
If you want to object to a comment made by another visitor, be sure to contact me and I'll look into it ASAP. Don't forget to mention the page link, or the Doc ID of the document.