Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Earlier this summer I posted two tutorials on using Windows Authentication with ASP.NET: Enabling Window Authentication within an Intranet ASP.NET Web Application and Implementing Role Based Security within ASP.NET using Windows Authentication and SQL Server.  I also linked to Scott Mitchell's great ASP.NET 2.0 Security, Membership and Roles Tutorials that cover how to use Forms Authentication and the new Membership/Roles APIs in ASP.NET for Internet based web applications.

These tutorials covers how to implement authentication on your site, which is the process of identifying who an incoming user is.  They also demonstrate how to implement role based management on your site, which allows you to logically group individual users into higher-level roles or groups (for example: "admins", "friends", "subscribers", etc).  The tutorials also demonstrate how to implement authorization rules to grant or deny users/roles access to visit individual pages or URLs within a site (the roles tutorial above also demonstrates how to show/hide menu nodes based on the permissions of the incoming user).

Adding Security Authorization Rules to Business and Data Layers

When you authenticate a user within an ASP.NET application, the authenticated user's identity will be automatically flowed throughout that user's request on the server.  What this means is that you don't need to manually pass a user's identity around from method to method or class to class.  This makes it much easier to implement security authorization rules throughout your application.

One little known feature in .NET is the ability to have the CLR automatically use this identity information to authorize a user's capabilities before instantiating a class, or accessing a method/property on it.  This makes it easy to add clean security authorization rules to your business and data layers without having to write much code.

All you need to do to implement this is to use the PrincipalPermissionAttribute within the "System.Security.Permissions" namespace and decorate it on the appropriate class or member on it.  For example:

using System;
using System.Security.Permissions;

[PrincipalPermission(SecurityAction.Demand, Authenticated = true)]
public class EmployeeManager
{
    [PrincipalPermission(SecurityAction.Demand, Role 
"Manager")]
    
public Employee LookupEmployee(int employeeID)
    {
       // todo
    }

    [PrincipalPermission(SecurityAction.Demand, Role 
"HR")]
    
public void AddEmployee(Employee e)
    {
       // todo
    }
}

In the above example, I have added a PrincipalPermission attribute to the "EmployeeManager" class.  By adding it I am requiring that a user must be authenticated (logged-in) before this class can be instantiated during a web request (the Authenticated=true demand enforces this).  I have also then added two additional security demands on the "LookupEmployee" and "AddEmployee" methods.  With the LookupEmployee method I am requiring that the authenticated user for the request is within the "Manager" role in order for the method to be invoked.  With the AddEmployee method I am requiring that the authenticated user for the request is within the "HR" role in order for this method to be invoked and have a new Employee added to the system.

And now if I accidentally introduce a security hole within my UI tier and have some code-path that allows a non-Manager/HR employee to cause these methods to be invoked, my business tier will automatically prevent this from happening and raise a security exception.  

The PrincipalPermissionAttribute isn't tied to any specific authentication mode.  It will work with Forms Authentication, Windows Authentication, Passport Authentication, or any custom authentication mode you want to invent.  It will also work with any Role implementation I might use (so if you build or plug-in your own Role Provider in ASP.NET it will just work).

The PrincipalPermissionAttribute type is implemented in the standard CLR mscorlib assembly that all .NET projects compile against.  So it isn't ASP.NET specific, and can be used within any application type (including Windows and Console applications).  In addition to making it more generically useful, this makes it easier to unit-test business/data libraries built with it.

Using PrincipalPermissionAttributes within Pages and Controls

The PrincipalPermissionAttribute can be used on any class within an application.  So in addition to using it within your business and data layers, you can also use it within ASP.NET pages or user-controls you author in your site as well.  For example, to enforce that your "MyPage" page can only be used by those within the "Manager" role, you could add a PrincipalPermission attribute to the code-behind of it (below done in VB):

Imports System.Security.Permissions

<PrincipalPermission(SecurityAction.Demand, Authenticated:
=True, Role:="Manager")> _
Partial Class MyPage
    
Inherits System.Web.UI.Page

End Class

More on ASP.NET Security

To learn more about ASP.NET security I'd recommend checking out my ASP.NET Security Resources list as well as my ASP.NET Tips and Tricks Page.

There are also now two dedicated books on ASP.NET 2.0 Security out there: Stefan Schackow's Professional ASP.NET 2.0 Security, Membership and Role Management book and the new Developing More Secure Microsoft ASP.NET 2.0 Applications book by Dominick Baier (who runs the great http://www.leastprivilege.com/ blog) that just got published this week:

Hope this helps,

Scott

Published Wednesday, October 4, 2006 8:50 AM by ScottGu

Comments

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Wednesday, October 4, 2006 3:50 PM by Plip
Nice trick! :-)

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Wednesday, October 4, 2006 4:29 PM by Jeff
There are so many neat things like this in the framework that I'm sure we'd all use if we knew they were there. Well done! This is cool stuff.

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Wednesday, October 4, 2006 10:52 PM by Cesar
Excellet Tip, I was looking for a way to validate the Roles against a control inside a page, this is the solution, is excelent, thanks a lot. Cesar

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Thursday, October 5, 2006 2:36 AM by MichaelF
The ideea of having class and method security permissions is excellent. But i can see a major shortcoming of the PrincipalPermissionAttribute: the developer must decide on the Role which has the execution permission. A nice feature would be if the Role is replaced by a tag. Then the tags, roles and their associations could be managed from somewhere else, for example AuthorizationManager (tags operations).

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Thursday, October 5, 2006 6:00 AM by Aaron
Scott, We really wanted to use these security features in ASP.net 1.1 app that we were writing for a large government customer. The problem was that it was a new organization and the “roles” were evolving and being re-defined and renamed on a continual basis as the org settled in. We couldn’t do anything with this because the roles in the security demands have to be hard coded in. At least as best as I could tell. It would be nice if these could be mapped to the web.config. Cheers, Aaron

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Thursday, October 5, 2006 10:20 AM by ScottGu

Hi Aaron,

The string values for roles should be mappable to resource strings - which would allow you to store these separately from the action code (allowing you to name the roles whatever you want).

Michael and Mike have a nice request above which is the ability to add another level of indirection, where the assertions are for capabilities and then a separate store is used to map which roles have those capabilities.

Permission stores like AZMan give you the ability to define capabilities<->roles<->user mappings like this.  In theory you could create a similar attribute to the PrincipalPermission one above that uses this store to perform those security checks.

Hope this helps,

Scott

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Thursday, October 5, 2006 11:03 AM by Dom
Scott, I thought this feature was limited to COM+. About the only use for COM+ that I can find is asynchronous execution. Nice to know about these features. Can I set the values in web.config, accessing them this way?

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Thursday, October 5, 2006 10:19 PM by Val
When a user without the access to the role specified tries to access the method, the SecurityException is thrown. How to catch that exception? If I use that attribute for a UserControl, I just would like that control not to render. However, I was not able to catch the Security Exception. Thanks.

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Friday, October 6, 2006 8:39 AM by NicoB
Scott, I tried to map the string value to a resource string but I keep getting an error when I compile the project: "An attribute argument must be a constant expression, typeof expression or array creation expression" Here's a code snippet: [System.Security.Permissions.PrincipalPermission(System.Security.Permissions.SecurityAction.Demand, Role = Resources.Resource.Role)] Please, can you give me a tip I can use to map the Role value to a resource string in order to change it without hardcoding it. Thx! NicoB.

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Friday, October 6, 2006 2:04 PM by Richard P

Workaround for dynamic role names:

Use nested groups on the MembershipProvider side.

For example, at the time of coding, you know the final name of the Managers group is going to change.  So you hard-code "DONOTUSE My Application Managers" and just make sure the final Managers group is in that group.

Obviously, this strategy won't work if your provider doesn't support nested membership.

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Tuesday, October 10, 2006 7:26 AM by Martin
Great article, thanks! Is it possible to specify the PrincipalPermission attribute in the ASPX instead of the code-behind (e.g. in the @Page directive)? This would allow to change the security without having to recompile the application. Thanks.

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Tuesday, October 10, 2006 10:59 AM by ScottGu

Hi Nico,

I checked with a few folks on my team, and unfortunately it looks like the PrincipalPermission attribute doesn't support pulling the role-name from a resource provider like I thought. :-(

Sorry about that,

Scott

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Tuesday, October 10, 2006 11:01 AM by ScottGu

Hi Martin,

There isn't a built-in way to set this via the page directive.  However, you could use this technique to extend the page directive to allow this to be specified: http://weblogs.asp.net/scottgu/archive/2005/08/02/421405.aspx

You could then use this within your base class to check the permissions.

Alternatively, I'd recommend using the web.config authorization model if you want to store these settings separately from the application code.

Hope this helps,

Scott

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Thursday, October 12, 2006 5:20 PM by Jeff Gonzalez
I think you can create an instance of PermissionPrincipal instead of using the attribute if you need more flexibility. It isn't as "neat" as using the declarative form, but it still works I think. Pseudocode Example: //Assume Resources.Roles.Manager is "Manager" try { PermissionPrincipal permPrincipal = new PermissionPrincipal( User.Identity.Name, Resources.Roles.Manager); permPrincipal.Demand(); } catch (SecurityException ex) { //handle noauthZ here }

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Monday, October 16, 2006 1:04 AM by ScottGu

Hi Johann,

You should be able to access the Profile and Membership providers from within your web-service.  You could then authorize any code called from the web-service.

Hope this helps,

Scott

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Monday, October 16, 2006 4:17 AM by Andy Mackie
My *business* requirement is for users to be able to define their own roles, and many different roles can have access to the same operation. For ease of administration, it should also be possible to have groups of operations that can be assigned to roles. In an enterprise-wide, multi-department/multi-company environment, I must also be able to assign a user different roles for different things. Nothing should be hard-coded. I would love to use the in-built .NET security that you describe, but it doesn't provide the features I need. Windows Authorization Manager (AzMan) does, but then that doesn't integrate at all into .NET security - I have to code against the AzMan API. It seems to me that the .NET and the AzMan teams at Microsoft need to work together to integrate these two. The declarative security could then be based on whether a user has access to a specific operation (via any assigned role/scope), rather than on whether the user is a member of a specific role. Any plans on integrating AzMan operations with .NET declarative security ?

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Tuesday, October 17, 2006 5:05 AM by Dirk
Hi There, Could someone tell me how to add users to roles? Unfortuantly it is asp.net 1.1 boo-hoo

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Tuesday, October 17, 2006 10:27 AM by ScottGu

Hi Dirk,

For learning how to implement a Roles-based solution for ASP.NET 1.1, I'd recommend reviewing the "Portal Starter Kit" here: http://www.asp.net/downloads/default11.aspx?tabid=62

It includes sample code that shows how to-do this.

Hope this helps,

Scott

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Thursday, October 19, 2006 3:09 PM by Edmund
I tried to apply this attribute to a class constructor, to allow exposing a constructor to a certain role. This failed compilation with a "Attribute 'PrincipalPermission' is not valid on this declaration type. It is valid on 'class, method' declarations only." Is there a way to apply security to constructors? Edmund

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Thursday, October 19, 2006 11:52 PM by ScottGu

Hi Edmund,

I don't believe there is a way to set it on a construtor (usually you'd just set it on the class - since the constructor is the first thing called when an object is created, and so setting it on the class level should have the same effect).

If you want to have some constructors have the permission but not others, one way to make it work would be to have an "Initialize" method that some of the secure constructors call - and then set the metadata on that method.

Hope this helps,

Scott

# re: Tip/Trick: Adding Authorization Rules to Business and Data Layers using PrincipalPermissionAttributes

Monday, December 25, 2006 3:49 AM by Prasad Khandekar

Hello,

   The approach you have mentioned does make application development simpler. However this approach may not be a good idea for services companies who are developing and maintaining applications for large number of customers. As for every authorization change (Role) the customer will have to come back to the company who has developed the application. Also this approach mau result in re-compilation of large number of code in the application upon such change.

For inhouse application however I would strongly recommend this approach.

Regards,

Prasad P. Khandekar