Saturday, December 25, 2004 4:30 PM bart

PSP Episode 4 - Role-based secury unleashed

Role-based security unleashed

Every .NET developer should (at least) have heard about role-based security. In this fourth episode of my "Personal Security Push" I'll talk about what role-based security is all about, how it works and how to empower it in your applications.

What is it?

Roles should build the bridge between the security built in in your code and the physical company structure (think of HR). It tells the code who can perform what action. In real life, your organization likely has various "roles", such as management, consultants, clerks, etc. These people all have different rights and therefore they need different access rules when performing actions in your application. The .NET Framework supports this by means of "role-based security", in which people are associated to their role. and security is applied based on that role. Typically you'll find roles useful to enforce business rules.

About authentication and authorization

In order to use an application, a user needs to do (either implicitly or explicitly) several things to get started. The first action is typically authentication. Authentication is a mechanism for the user to prove that he/she is really who he/she pretends to be (think of passwords, smartcards, fingerprints, eyescans, RFID identification, etc). In order to make this possible, the user and the system need to share a secret in order to be able to validate the user. When authentication succeeds, the system can examine the users database (whatever format it has, Active Directory, SAM, SQL Server, etc) possibly in combination with other repositories (AD/AM, AzMan, SQL Server, etc) to determine the roles for the user. As an example, Windows authentication uses the SAM or Active Directory to associate the groups with a user on logon (combined with privileges this results in the user token that can be queried via the whoami command). On the authentication phase has completed, authorization comes into play throughout the lifetime of the user's session, to determine whether the user has the appropriate rights (based on the user's identity and/or the roles of the user) to perform certain actions (e.g. access the filesystem, make changes to a database, or on a higher level, to perform certain business actions).

IPrincipal and IIdentity

The .NET Framework supports role-based security via the System.Security namespace in general. More specifically, there are two protagonists in the System.Security.Principal namespace that are crucial in the whole story:

  • System.Security.Principal.IPrincipal: once the user has been authenticated, a principal for the user is created, which contains the user identity (IIdentity in property Identity) and the roles (method IsInRole) that the user belongs to; depending on the authentication type, a certain implementation of this interface is used (e.g. WindowsPrincipal for Windows authentication, GenericPrincipal for custom authentication)
  • System.Security.Principal.IIdentity: the principal also contains information about the user himself/herself, i.e. the user's name and flags about the authentication type and a boolean indicating whether the user was authenticated or not (to detect anonymous users in an application, see Episode 3 on null sessions)

If it helps you, you can take a look at the principal as it were the managed equivalent of a user token, but it contains less information.

Assign the roles to a user

When using Windows authentication, the roles assignment for a user is performed automatically, based on the Windows groups the user belongs to. When accessing the WindowsPrincipal object you can query the roles of the users via the IsInRole method. When using forms authentication, you can use the GenericPrincipal object to represent the user:

FormsIdentity id = ...; //retrieve the identity in some way, e.g. by using FormsAuthentication.Decrypt on the authentication cookie
GenericPrincipal gp = new GenericPrincipal(id, roles); //roles is a string array with the roles
Context.User = gp;

Typically, you'll execute this kind of code after authentication or on Application_AuthenticateRequest event handler in global.asax. As complete example can be found on http://msdn.microsoft.com/library/default.asp?url=/library/en-us/dnnetsec/html/SecNetHT04.asp.

Use the roles in ASP.NET

Once the roles are present, you can use these roles to make decisions about the user rights. In ASP.NET as possible way to do this is by using the web.config file:

<authorization>
   <allow roles="role1" />
   <allow roles="role2" />
   <deny users="*" />
</authorization>

Combined with the location tag, you can grant only specific roles access to certain parts of the website:

<location path="/admin">
   <authorization>
      <allow roles="admin" />
      <deny users="*" />
   </authorization>
</location>

However, this is only the tip of the iceberg. In any kind of application, you can use the role-based security on the code level as well.

Declarative Security versus Imperative Security

Both on the class level and the member level (method, property, delegates, etc) you can apply declarative security. Declarative security is attribute-based (defined in System.Security.Permissions) and fairly straightforward:

[PrincipalPermission(SecurityAction.Demand, Role="SomeRole")]
public class MyClass
//...

or on the member level:

public class SomeClass
{
   //...
   [PrincipalPermission(SecurityAction.Demand, Role="SomeRole")]
   public void SomeMethod()
   //...

For Windows authentication, you'll use an attribute like this:

[PrincipalPermission(SecurityAction.Demand, Role=@"MYDOMAIN\TheGroup")]

Applying this attribute performs checking at runtime and if the current user's principal does not comply to the requirements in the attribute declaration, a SecurityException will be thrown.

An altnerative is imperative security. Using this mechanism, you write code to check the user's roles and take the right action based on the result. In fact there are two kinds of checks that can be performed at runtime. The first one is to "demand" security, just like we've done with declarative security. This will throw a SecurityException if the Demand call does not succeed. Another one is checking the role in a more flexible way using the IsInRole method. Let's show:

//Pure imperative security
new PrincipalPermission(null, "SomeRole").Demand();

//Manual role membership check
WindowsPrincipal wp = ...; //retrieve it principal in some way, in ASP.NET by using HttpContext.Current.User for example
if (wp.IsInRole("SomeRole"))
   //do it
else
   //throw an exception or take another action to tell the user that he/she is not allowed to perform this action

What mechanism to use?

Declarative security has the following (dis)advantages:

  • Can be attached on the class level so that it applies to all members of the class. Imperative security is less flexible on this point.
  • A security demand implemented through an attribute is executed before all the other code in the method, thus the code in the method has no chance to execute if the security demand fails. With imperative security, you have to do the work yourself and make sure it's right.
  • You can query the security demands when doing deployment by using tools like permview.exe:

    permview.exe /decl MyApp.exe

    Microsoft (R) .NET Framework Permission Request Viewer.  Version 1.1.4322.573
    Copyright (C) Microsoft Corporation 1998-2002. All rights reserved.

    Class Test Demand permission set:
    <PermissionSet class="System.Security.PermissionSet" version="1"/>

    Class Test NonCasDemand permission set:
    <PermissionSet class="System.Security.PermissionSet" version="1">
       <Permission class="System.Security.Permissions.PrincipalPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1">
          <Identity Authenticated="true" Role="test"/>
       </Permission>
    </PermissionSet>

    Method Test::Do() Demand permission set:
    <PermissionSet class="System.Security.PermissionSet" version="1"/>

    Method Test::Do() NonCasDemand permission set:
    <PermissionSet class="System.Security.PermissionSet" version="1">
       <Permission class="System.Security.Permissions.PrincipalPermission, mscorlib, Version=1.0.5000.0, Culture=neutral, PublicKeyToken=b77a5c561934e089" version="1">
          <Identity Authenticated="true" Role="test"/>
       </Permission>
    </PermissionSet>

    In IL (using ildasm.exe), you'll find the security permissionset demands in the beforefieldinit section of the class (or if applied on member level on top of the IL code):

    .class private auto ansi beforefieldinit Test
           extends [mscorlib]System.Object
    {
      .permissionset demand = (3C 00 50 00 65 00 72 00 6D 00 69 00 73 00 73 00   // <.P.e.r.m.i.s.s.
                               ...
      .permissionset noncasdemand = (3C 00 50 00 65 00 72 00 6D 00 69 00 73 00 73 00   // <.P.e.r.m.i.s.s.
                                     ...

    .method private hidebysig static void  Do(int32 a,
                                              int32 b) cil managed
    {
      .permissionset demand = (3C 00 50 00 65 00 72 00 6D 00 69 00 73 00 73 00   // <.P.e.r.m.i.s.s.
                               ...
      .permissionset noncasdemand = (3C 00 50 00 65 00 72 00 6D 00 69 00 73 00 73 00   // <.P.e.r.m.i.s.s.
                                     ...
  • Performance is better than with imperative security because it's evaluated only once when loading. This kind of optimization cannot be applied when the code is embedded inside your own code blocks.
  • One of the drawbacks is that the security is embedded in the assembly and not configurable (which can also be an advantage). Be sure to look at the pitfalls section further in this post, since this can be a showstopper for declarative security.

Imperative security (dis)advantages:

  • Based on variables (see pitfalls for more info on why this is great), can be configured using any mechanism you want to support.
  • Your code is in charge of security, by means of condiational logic etc.
  • Generally spoken, more flexible but less general and negative performance impacts.

More about System.Security.Permissions

I'll cover other attributes in this namespace in a later post. There are over 20 permission types in this namespace that can be used throughout your code to implement a real tight security in depth strategy. Check out next episodes on CAS (code access security) for more information about this.

Pitfalls

Be careful when using roles such as BUILTIN\Administrators. In localized versions of Windows, this name can be different (e.g. BUILTIN\Administradores or BUILTIN\Administrateurs). Make this configurable!

Del.icio.us | Digg It | Technorati | Blinklist | Furl | reddit | DotNetKicks

Filed under:

Comments

# re: PSP Episode 4 - Role-based secury unleashed

Sunday, December 26, 2004 12:41 AM by bart

Correction concerning the <location> tag:

<location path="/admin">
<authorization>
<allow roles="admin" />
<deny users="*" />
</authorization>
</location>

should be

<location path="/admin">
<system.web>
<authorization>
<allow roles="admin" />
<deny users="*" />
</authorization>
</system.web>
</location>

# Bart's PSP (Personal Security Push)

Monday, December 27, 2004 6:34 AM by TrackBack

# Bart's PSP (Personal Security Push)

Monday, December 27, 2004 6:35 AM by TrackBack

# re: PSP Episode 4 - Role-based secury unleashed

Monday, December 27, 2004 9:56 AM by bart

Be careful when using roles such as BUILTIN\Administrators. In localized versions of Windows, this name can be different (e.g. BUILTIN\Administradores or BUILTIN\Administrateurs). Make this configurable!

use WindowsBuiltInRole enum....

dominick

# re: PSP Episode 4 - Role-based secury unleashed

Thursday, December 30, 2004 5:49 AM by bart

Thanks for the comment Dominick. I'll be blogging about these enums soon (as well as enhancements in the v2.0 space concerning ACLs, SIDs etc without direct Win32 calls), the pitfall was just some last thing I had in my mind for that particular (looooong) post.

However, the major problem I came across some time ago was to make web.config <authorization> sections flexible enough to work with Administrators only access but still make it configurable. BUILTIN\Administrators is allowed in the <allow roles=""> tag, but that is fixed for a certain locale. SIDs don't work over there...