Practical .NET

Going Beyond Usernames and Roles with Claims-Based Security in .NET 4.5

Claims-based security lets you manage your site's authorization process using any criteria that makes sense to you. And the Microsoft .NET Framework 4.5 provides some performance support for you once you start using claims-based security.

In an earlier column, I showed how to create a ClaimsPrincipal object and insert it into your ASP.NET authorization process. In that article I showed how claims-based security duplicates your existing roles- and identity-authorization processes. But the beauty of claims-based security is that your authorization processes can move beyond "names and roles." The first step in doing that is to retrieve additional information about the user from your organization's database and add that to the ClaimsPrincipal's Claims collection. The second step is to use those claims in your application's authorization process.

While code for retrieving user information and adding additional Claims could be put in your custom ClaimsPrincipal object's constructor, it would tie your ClaimsPrincipal to the authorization needs of a specific application. It makes more sense to put the code that adds additional Claims in the HTTP module that creates the ClaimsPrincipal. That strategy allows the applications using your custom ClaimsPrincipal object to add the claims they need.

The code in Listing 1 retrieves some data about the current identity and creates two Claims: one Claim uses the predefined ClaimTypes.Email type; the other Claim uses a custom "Region" claim type I've created (http://phvis.com/identity/claims/region). Using a URI for my Region claim isn't required -- I could've just used the string "region" -- but I've chosen to follow the pattern of the existing Claims types. Because the ClaimsPrincipal's Claims collection can only be updated from within the ClaimsPrincipal object, the code adds both Claims to a list and passes that list to the ClaimsPrincipal object's constructor.

Listing 1. Creating a ClaimsPrincipal with claims.
private static ClaimsPrincipal CreateClaimsBasedPrincipal(string UserName)
{
  Claim cl;
  List<Claim> cls = new List<Claim>();
  PHVUser usr;

  using (DataContext dc = new DataContext))
  {
    usr = dc.Users.Single(u => u.UserName = UserName);
    cl = new Claim(ClaimTypes.Email, usr.Email);
    cls.Add(cl);
    cl = new Claim("http://phvis.com/identity/claims/region", usr.Region);
    cls.Add(cl)
    PHVClaimsPrincipal cp = new 
      PHVClaimsPrincipal(UserName, "manager, admin", cls);
  }

Inside the custom ClaimsPrincipal, you just need to add the roles to the Claims collection that will (eventually) be used to create the ClaimsIdentity object:

public PHVClaimsPrincipal(string userName,string UserRoles="", 
    List<Claim> Claims = null)
{
  List<Claim> cls = new List<Claim>();
  if (Claims != null)
  {
    cls.AddRange(Claims);
  }

In code in a Web Form, controller, or view, you can retrieve the ClaimsPrincipal from the User property, check to see if a Claim exists in the ClaimsPrincipal object's Claims collection and, if it does, see what value the Claim has been set to. This code, for example, checks to see if my region claim exists in the ClaimsPrincipal's Claims collection and, if it does, that the Claim holds the correct value:

PHVClaimsPrincipal cp = (PHVClaimsPrincipal)Thread.CurrentPrincipal;
Claim cl = cp.Claims.FirstOrDefault(
      c => c.Type == "http://phvis.com/identity/claims/region");
if (c != null && cl.Value == "West")
{

Improving Performance
Right now, the code that creates the ClaimsPrincipal is going to be executed on every request to the site. Accessing the database to retrieve claims-related information on each request is going to be a drag on performance. To help with that, the Microsoft .NET Framework 4.5 includes support for writing a ClaimsPrincipal with all of its claims out to a cookie. To use this feature you'll actually make more configuration changes than write actual code.

First, in your web.config or app.config file, you need to set up your site to read the required configuration section by adding the following section element to the configSections element (the configSections element must be the first element after the configuration element's open tag):

<configSections>
  <section name="system.identityModel.services"   	 
   type="System.IdentityModel.Services.Configuration.
                SystemIdentityModelServicesSection, 
 System.IdentityModel.Services, Version=4.0.0.0, 
 Culture=neutral, PublicKeyToken=B77A5C561934E089" />

With that in place, you can add the configuration elements that support creating the cookie:

<system.identityModel.services>
  <federationConfiguration>
    <cookieHandler requireSsl="false" />
  </federationConfiguration>
</system.identityModel.services>

In this example, I've turned off the requirement for SSL by setting the cookieHandler tag's requiresSsl attribute to false. While that simplifies testing, in a production system you'll want to remove that attribute and use SSL to prevent your cookie from being stolen.

The next step in configuring your site is to use an add tag inside the modules element to add the module that handles reading and writing the cookie:

<modules>
  <add name="SessionAuth" 
       type="System.IdentityModel.Services.SessionAuthenticationModule, 
             System.IdentityModel.Services, Version=4.0.0.0, 
             Culture=neutral, PublicKeyToken=b77a5c561934e089"/>

With all of that configuration done, you can start writing code. In my HTTP module, after calling the method that creates the ClaimsPrincipal object, I first create a SessionSecurityToken, passing my ClaimsPrincipal and an expiry time for the cookie (this is an absolute time, not a sliding expiry time, so I've put it well out into the future). I then use the SessionAuthenticationModule (available from the FederatedAuthentication object) to write out the cookie:

SessionAuthenticationModule ssm = 
  FederatedAuthentication.SessionAuthenticationModule;
            
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
  ClaimsPrincipal cp = CreateClaimsBasedPrincipal();
  SessionSecurityToken sst;
  sst = new SessionSecurityToken(cp, TimeSpan. (8));
  ssm.WriteSessionTokenToCookie(sst);
}

If you have a lot of Claims associated with your ClaimsPrincipal, consider setting the SessionSecurityToken's IsReferenceMode property to true. This causes the ClaimsPrincipal to be saved on the server with just a reference to the ClaimsPrincipal stored in the cookie. Of course, this won't work in a server farm without tying users to specific servers.

With the cookie in place, before creating my ClaimsPrincipal, I'll check to see if the cookie exists and, if it does, create my ClaimsPrincipal from it. Again, the SessionAuthenticationModule takes care of most of the work. I first use the object to see if the cookie is in the HttpContext's Cookies collection. If the cookie is present, I retrieve it through the SessionAuthenticationModule's ContextSessionSecurityToken property. Finally, I use the AuthenticateSessionSecurityToken method (passing the cookie and a false to indicate that I don't want the cookie updated) to update my ClaimsPrincipal and pass it to ASP.NET:

if (ssm.ContainsSessionTokenCookie(
  HttpContext.Current.Request.Cookies))
{               
  var ck = ssm.ContextSessionSecurityToken;
  ssm.AuthenticateSessionSecurityToken(ck, false);
}            
else
{
  if (HttpContext.Current.User.Identity.IsAuthenticated)
  {

Declarative Security
With performance issues taken care of, I want to have similar declarative support for claims-based security as I do now for roles- and identity-authorization. To support declarative authorization with claims that aren't names or roles, the .NET Framework 4.5 provides the ClaimsPrincipalPermission attribute (from the System.IdentityModel.Services library). The ClaimsPrincipalPermission attribute provides three values for controlling whether access should be allowed or denied: The security action (usually SecurityAction.Demand) and two properties: Resource and Operation. The Resource property is a string that's intended to indicate what the request is attempting to access; the Operation property is intended to hold a string that describes what the request will do. You're free, however, to develop whatever conventions you want around using these properties.

Here's an example of a Web API method decorated with the ClaimsPrincipalPermission attribute that specifies the request is to retrieve billing information:

[ClaimsPrincipalPermission(SecurityAction.Demand, 
  Operation="Retrieve", Resource="Billing")]
public HttpResponse Get(string id)
{

The attribute can also be applied to pages and methods in an ASP.NET Web Forms application:

[ClaimsPrincipalPermission(SecurityAction.Demand, 
  Operation="Update", Resource="Customer")]
public partial class CustomerUpdate : System.Web.UI.Page
{

A class or method can be decorated with multiple ClaimsPrincipalPermission attributes, and if one of the attributes denies access, the request is denied.

In addition to being used declaratively, a ClaimsPrincipalPermission object can also be created in code and processed by calling its Demand method (when creating a ClaimsPrincipalPermission in code, you pass only two parameters: Resource and Action). You can also combine ClaimsPrincipalPermission objects using their Union and Intersect methods or convert them to and from XML for storage or transmission. This example creates two ClaimsPrincipalPermission objects and then joins them together into one set before processing:

ClaimsPrincipalPermission BillUpdate = 
  new ClaimsPrincipalPermission("Billing", "Update");
ClaimsPrincipalPermission BillDelete = 
  new ClaimsPrincipalPermission("Billng", "Delete");
IPermission AllPermissionsRequired = 
  BillUpdate.Union(BillDelete);
AllPermissionsRequired.Demand();

The ClaimsPrincipalPermission object forwards its processing to the application's ClaimsAuthorizationManager, which is responsible for allowing or denying access. Unlike the ASP.NET MVC/Web API model, where you create multiple custom Authorize or PrincipalPermission attributes, with the .NET Framework 4.5, all of your application's authorization code is centralized in the application's ClaimsAuthorizationManager.

To create your own authorization manager, first create a class that inherits from ClaimsAuthorizationManager (you'll need references to the System.Security and System.IdentityModel libraries) and override its CheckAccess method. The method should return false when access is denied, so your initial version of the class should look like this:

class PHVClaimsAuthorizationManager : 
  ClaimsAuthorizationManager
{
  public PHVClaimsAuthorizationManager()
  {
  }

  public override bool CheckAccess(AuthorizationContext context)
  {
       
    return false;
  }  
}

The CheckAccess method is passed an AuthorizationContext object, which has two properties that hold collections of Claims (typically, one) based on the values set in the ClaimsPrincipalPermissions attribute. The Action property's Claim holds the value of the attribute's Operation property; the Resource's Claim holds the value of the attribute's Resource property.

Listing 2 shows typical code that accepts values from a ClaimsPrincipalPermission attribute and checks them against a Claim associated with the identity making the request.

Listing 2. A custom method for authorizing claims.
public override bool CheckAccess(AuthorizationContext context)
{
  if (context.Action.First().Value == "Retrieve" &&
      context.Resource.First().Value == "Billing")
  {
    Claim cl = context.Principal.Claims.SingleOrDefault(
          c => c.Type == "http://phvis.com/identity/claims/region");
    if (cl != null &&
        cl.Value == "West")
    { 
      return true;
    }                
  }
  return false;
}

To have your ASP.NET application use your ClaimsAuthorizationManager, you must first add another section element inside your configSections element. This element specifies the handler that the .NET Framework provides for processing the system.identityModel element:

<configuration>
  <configSections>
    <section name="system.identityModel"
             type="System.IdentityModel.Configuration.SystemIdentityModelSection, 
                   System.IdentityModel, Version=4.0.0.0, Culture=neutral, 
    PublicKeyToken=B77A5C561934E089"/>
  </configSections>

You can now add a system.identityModel element to your web.config file (or to your app.config file for a self-hosted Web API service). Within the system.identityModel element, add an identityConfiguration element and, within that, a claimsAuthorizationManager tag. Set the type attribute on that tag to the full name for your custom ClaimsAuthorizationManager class (namespace and class name) followed by a comma, followed by the name of its DLL. For my PHVClaimsAuthorizationManager in an application called PHVWebAPI, the elements look like this:

<system.identityModel>
  <identityConfiguration>
    <claimsAuthorizationManager 
      type="PHVWebAPI. PHVClaimsAuthorizationManager,PHVWebAPI"/>
  </identityConfiguration>
</system.identityModel>

With those changes, you've extended your application to use claims-based authentication.

While claims-based authorization is often assumed to just be a stepping-stone on the way to integrating with federated security, it's really more than that. Claims-based security gives you the ability to implement a flexible authorization scheme, based on information about the user -- a system that does far more than a rigid names-and-roles system ever could.

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/.

comments powered by Disqus

Featured

  • Compare New GitHub Copilot Free Plan for Visual Studio/VS Code to Paid Plans

    The free plan restricts the number of completions, chat requests and access to AI models, being suitable for occasional users and small projects.

  • Diving Deep into .NET MAUI

    Ever since someone figured out that fiddling bits results in source code, developers have sought one codebase for all types of apps on all platforms, with Microsoft's latest attempt to further that effort being .NET MAUI.

  • Copilot AI Boosts Abound in New VS Code v1.96

    Microsoft improved on its new "Copilot Edit" functionality in the latest release of Visual Studio Code, v1.96, its open-source based code editor that has become the most popular in the world according to many surveys.

  • AdaBoost Regression Using C#

    Dr. James McCaffrey from Microsoft Research presents a complete end-to-end demonstration of the AdaBoost.R2 algorithm for regression problems (where the goal is to predict a single numeric value). The implementation follows the original source research paper closely, so you can use it as a guide for customization for specific scenarios.

  • Versioning and Documenting ASP.NET Core Services

    Building an API with ASP.NET Core is only half the job. If your API is going to live more than one release cycle, you're going to need to version it. If you have other people building clients for it, you're going to need to document it.

Subscribe on YouTube