Practical .NET
Leveraging Claims-Based Security in ASP.NET 4.5
Microsoft .NET Framework 4.5 support for claims-based security can make your existing authorization system more powerful and flexible, even if you never intend to start working with third-party security providers. Plus, it's backward-compatible with virtually all of the authorization code you're already using.
There's nothing wrong with using roles to manage authorization. However, most organizations find themselves having to implement one of three strategies with roles, two of which aren't satisfactory -- and the third of which could be so much better. The most common strategy for roles is "wide access": each role's access is based on the user who needs the "most" access. The result is most users are given a role that has more access than they actually need, which is bad from a security point of view. The second strategy is "narrow access": limited roles provide the user with just the access he needs. The result of this strategy is a proliferation of roles, with many having very few members (and sometimes just one). Under this strategy, as users' jobs change, new roles have to be crafted and users redistributed across them. The narrow access strategy is bad from an administrative point of view.
The third strategy is to create roles with very limited access and then assign each user to multiple roles. This is often the most satisfactory both from a security point of view (users' access is limited to what they need) and from an administrative point of view (roles are added to and removed from users rather than having to migrate users between roles). But this third strategy might be best described as "claims based" because it's actually just a proxy for claims-based security. Each role assigned to a user actually represents a claim about the user: the user isn't in management; the user hires contractors; the user is in the sales division.
In claims-based security, after a user is authenticated and assigned an identity, the identity is assigned not roles, but claims. In an ASP.NET Web Forms or ASP.NET MVC application, those claims can be based on information about the user stored in the application's membership database. In a Web API service, the claims can also be received from the calling application through tokens in a SOAP header or a cookie. Together, an identity and the claims assigned to the identity describe a principal, which is what ASP.NET then uses for authorization.
The Microsoft .NET Framework 4.5 provides support for claims-based security that, to a very large extent, is backward-compatible with the authorization framework from previous versions of the .NET Framework. Integrating claims-based security into new or existing applications gives you the ability to create a flexible authorization framework driven by what's known about the identity associated with an existing request. To make that happen, you have to do three things: create your own ClaimsPrincipal class, create your own ClaimsAuthorization class and intercept the ASP.NET processing pipeline just after a user's identity is created. What you don't have to do is integrate with a federated security framework (though moving to claims-based authorization will position you to do that).
Creating a ClaimsPrincipal Object
The simplest way to integrate claims-based authorization into most ASP.NET applications is to add an HTTP module -- a class that implements the IHttpModule and IDisposable interfaces -- to the ASP.NET processing pipeline (the only place this won't work is in a self-hosted Web API service where you'll need to use a message handler). In the class Init event, tie a method of your own to the PostAuthenticateRequest event (this event fires after ASP.NET has created the identity for an authenticated request). For now, in that method (and after checking that the user has been authenticated by ASP.NET) I'll just call another method of my own. In that method I'll create a claims-based principal and pass it to ASP.NET to be used for authorization:
public class PHVClaimsBasedAuthorization :
System.Web.IHttpModule, IDisposable
{
public void Init(System.Web.HttpApplication context)
{
context.PostAuthenticateRequest += ReplacePrincipal;
}
private static void ReplacePrincipal(object sender, EventArgs e)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
ClaimsPrincipal cp = CreateClaimsBasedPrincipal();
The first step in creating a claims-based principal is to define a class that inherits from the ClaimsPrincipal class. Initializing the resulting class is a multi-step, Russian-nesting-doll kind of process: A ClaimsPrincipal object holds at least one ClaimsIdentity object, which can hold a GenericIdentity object (a holdover from the previous security framework), which, in turn, holds the user name. In a claims-based environment, when creating the GenericIdentity object, you must not only pass the user name but also a string indicating the authentication source (this can be any arbitrary string).
The authorization-related properties and collections on a ClaimsPrincipal are read-only, so configuring the ClaimsPrincipal must be done with code inside the ClaimsPrincipal. A typical ClaimsPrincipal constructor, therefore, accepts a username, creates a GenericIdentity object, uses that GenericIdentity to create a ClaimsIdentity object, and then adds that ClaimsIdentity object to the ClaimsPrincipal object Identities collection. That's what this code does:
public class PHVClaimsPrincipal : ClaimsPrincipal
{
public PHVClaimsPrincipal(string userName)
{
GenericIdentity gi = new GenericIdentity(username,
"PHV Custom Authentication");
ClaimsIdentity ci = new ClaimsIdentity(gi);
this.AddIdentity(ci);
}
Code in my HTTP Module to create the ClaimsPrincipal first retrieves the user's name from the identity created by the ASP.NET authentication process, then passes that name to my ClaimsPrincipal constructor:
private static ClaimsPrincipal CreateClaimsPrincipal()
{
string UserName = Thread.CurrentPrincipal.Identity.Name;
PHVClaimsPrincipal cp = new PHVClaimsPrincipal(UserName);
To have the ClaimsPrincipal object be used by the ASP.NET authorization process, the object must be put in the CurrentPrincipal property of the Thread object. ASP.NET also requires the principal to be in the User property of the Current property on the HttpContext object
unless you're in a self-hosted Web API service where the HttpContext isn't available. This code, which would follow the previous code that created the principal, handles both environments:
Thread.CurrentPrincipal = cp;
if (HttpContext.Current != null)
{
HttpContext.Current.User = cp;
}
return cp;
The method also finishes by returning the ClaimsPrincipal object to the calling method.
Compatibility
Because the .NET Framework 4.5 always works with ClaimsPrincipal objects, my HTTP module also demonstrates that existing authentication code that drills down to the User Identity property continues to work with ClaimsPrincipal objects. The ClaimsPrincipal also supports the old IPrincipal interface, so this code also works:
IPrincipal ip = this.User;
The same is true of the IsInRole method on the User object: It works the same way in the .NET Framework 4.5 with the ClaimsPrincipal object as it did in previous versions of the .NET Framework. The ClaimsPrincipal object is also compatible with the ASP.NET MVC and Web API Authorize and PrincipalPermissions attributes. This code, for example, would just require the ClaimsPrincipal object to have been created with a GenericIdentity object set to "Peter":
[Authorize(Users="Peter")]
public ActionResult Index(string id)
{
Similarly, the ClaimsPrincipal object will work with the authorization element in ASP.NET Web Forms. This example would also allow ClaimsPrincipal objects created with a GenericIdentity of "Peter" onto the site:
<authorization>
<allow users="Peter"/>
<deny users="*"/>
</authorization>
You can add multiple ClaimsIdentities to a ClaimsPrincipal (though Microsoft describes this as "unusual"). If you do so, the ClaimsPrincipal Identity property returns the first ClaimsIdentity added to the Identities collection. The ClaimsIdentity returned from the Identity property is also the only ClaimsIdentity used by the Authorize attribute when authorizing by user name. If you want to assign multiple identities, you can process the other identities in code through the ClaimsPrincipal Identities collection.
Adding Claims: Roles
Rather than assigning users to roles, the ClaimsPrincipal object has a collection of Claim objects, some of which represent claims about roles. For backward-compatibility, the claims-based system in the .NET Framework 4.5 treats claims about roles as the equivalent to previous versions of .NET Framework support for roles. Within a ClaimsPrincipal object, you can add Claim objects (including role Claims) to a ClaimsIdentity object Claims collection using the ClaimsIdentity AddClaim method.
Each Claim in the Claims collection consists of a type indicating the kind of Claim and a value. Many common Claims types, including role Claims, are identified through industry-standard URIs. For convenience, those URIs are set up in the .NET Framework 4.5 as values in the ClaimsType class. Microsoft has also added some additional Claims types of its own to the ClaimsType class.
The custom ClaimsPrincipal object shown in Listing 1 accepts an optional string parameter that lists role names. The constructor then adds those roles, as role Claims, to the ClaimsIdentity object it creates as part of initializing itself.
Listing 1. A custom ClaimsPrincipal object accepting an optional string parameter that lists role names.
public PHVClaimsPrincipal(string userName,string UserRoles="")
{
GenericIdentity gi;
ClaimsIdentity ci;
Claim cl;
gi = new GenericIdentity(userName, "PHV custom authentication");
ci = new ClaimsIdentity(gi);
foreach (string rol in UserRoles.Split(',').
Select(r => r.Trim()).ToArray())
{
cl = new Claim(ClaimTypes.Role, rol);
ci.AddClaim(cl);
}
this.AddIdentity(ci);
}
My CreateClaimsPrincipal method can now retrieve role names from some membership database and use them when creating my custom ClaimsPrincipal class. This example adds the identity to the manager and admin roles:
PHVClaimsPrincipal cp = new PHVClaimsPrincipal(UserName, "manager, admin");
If you add multiple identities to a ClaimsPrincipal, all the Claims from all identities are gathered together into the ClaimsPrincipal Claims collection. The Authorization attribute and authorize elements use the Claims collection to validate role claims; so when authorizing by roles, all the roles assigned to all of the ClaimsPrincipal identities are used.
If you want, you can abandon the GenericIdentity object when creating a ClaimsPrincipal. Instead, you can add a claim about the user name to the ClaimsPrincipal object. If you do, when creating the ClaimsIdentity object, you must specify which claims are to be used for username and role authorization. The code shown in Listing 2 creates a list of Claims, including an industry-standard name Claim. Then, when creating the ClaimsIdentity object, the code passes the list of Claims, the authentication string, and two parameters that specify that the name Claim object is to be used for identity authorization and Role Claim objects for role authorization.
Listing 2. Code creating a list of Claims.
Claim cl;
List<Claim> cls = new List<Claim>();
cl = new Claim(ClaimTypes.Name, "Peter");
cls.Add(cl);
foreach (string rol in UserRoles.Split(',').
Select(r => r.Trim()).ToArray())
{
cl = new Claim(ClaimTypes.Role,rol);
cls.Add(cl);
}
ci = new ClaimsIdentity(cls, "PHV Custom Authentication",
ClaimTypes.Name, ClaimTypes.Role);
this.AddIdentity(ci);
While claims-based security is backward-compatible with declarative authorization in pre-.NET 4.5 apps, code that performs authorization activities may require tweaking. If you access the CurrentPrincipal object from the User property in code, you'll need to cast it to the ClaimsPrincipal type (the User property is typed as IPrincipal). If you've implemented Cross-Site Request Forgery (CSRF) protection, you must add claims for ClaimTypes.NameIdentifier and for the URI http://schemas.microsoft.com/accesscontrolservice/2010/07/claims/identityprovider to your ClaimsPrincipal.
But this all just gives you backward compatibility with the old "names and roles" process. Now that you've inserted a ClaimsPrincipal object into your ASP.NET authorization process, you can move beyond that, a topic I'll pick up next month.
About the Author
Peter Vogel is a system architect and principal in PH&V Information Services. PH&V provides full-stack consulting from UX design through object modeling to database design. Peter tweets about his VSM columns with the hashtag #vogelarticles. His blog posts on user experience design can be found at http://blog.learningtree.com/tag/ui/.