Thursday 14 February 2013

Claims Based Access Control

The previous article on Role Based Access Control described the IIdentity and IPrincipal implementations available with Microsoft .NET 1.0 through 4.0. Designed in 2002, these soon began to creak under the stress of increasingly distributed systems of rising complexity. With WCF in 2006, Microsoft provided another security model incompatible with the old one, and in 2009, succeeded in combining the best parts of these two implementations into WIF, and ultimately in 2012, into .NET 4.5.

IIdentity and IPrincipal Revisited

The new hotness was Claims Based Authorization. A System.Security.Claim is a statement about an entity, made by another entity known as the Issuer of the claim. The statement takes the form of a key/value pair, which is obviously much more powerful than the true/false settings used in Role Based. For example, Active Directory might issue claims that the current user is named "alice", is an administrator, has email address "alice@microsoft.com", and so on. We would treat these claims, coming from AD, with a high level of confidence.
public class Claim
{
  public virtual string Type { get; }
  public virtual string Value { get; }
  public virtual string Issuer { get; }
  // ...
}
We could set up claims using syntax like this:
var claim = new Claim("name", "alice");
However, recall that previously we found reasons (of localization and breaking admin changes) to avoid using literal labels to identify security resources such as groups. It turns out that we might also want to use the predefined members - essentially namespace URIs - of the ClaimTypes class, to ensure that our claims can be serialized, shared and understood across a federated identity space:
var claims = new List<Claim>
{
  new Claim(ClaimTypes.Name, "alice"),
  new Claim(ClaimTypes.Email, "alice@microsoft.com"),
  new Claim(ClaimTypes.Role, "Sales"),
  new Claim(ClaimTypes.Role, "Marketing")
};
ClaimsIdentity and ClaimsPrincipal

Obviously, claims can carry a lot more detailed information about an entity than roles. For excellent reasons of backward compatibility, claims were integrated into the existing IIdentity / IPrincipal framework through the creation of two new container classes:
public class ClaimsIdentity : IIdentity
{
  public virtual IEnumerable<Claim> Claims { get; }
  // ...
}

public class ClaimsPrincipal : IPrincipal
{
  public virtual IEnumerable<ClaimsIdentity> Identities { get; }
  // ...
}
Now we can set up Alice's principal like this:
var id = new ClaimsIdentity(claims, "Console App");
var user = new ClaimsPrincipal(id);
Thread.CurrentPrincipal = user;
The second parameter to the ClaimsIdentity constructor, authenticationType, is needed because, unlike the case with Role Based Identity, Claims Based Identity allows claims to be attached to e.g. anonymous principals. By default then, if authenticationType is not provided, IsAuthenticated will return false.

Backward Compatibility

Examining the framework source, it's interesting to note that every (non static) public in these new classes is virtual. That's because they have been "interposed" in .NET 4.5, so that all the implementation classes we looked at last time (WindowsIdentity / WindowsPrincipal, and GenericIdentity / GenericPrincipal) as well as those we didn't (e.g. FormsIdentityRolePrincipal) are now derived from these common base classes. This design gives all consumer code access to the claims collection if desired, while still preserving the old interfaces (Name, IsInRole, etc).

The base classes implement the legacy security model with the help of the claims collection. For example, accessing the ClaimsIdentity.Name property causes this collection to be traversed, searching for an entry with type ClaimTypes.Name, while checking for a role is done by searching for ClaimTypes.Role entries.

Incidentally, both of these associations can be overridden using the supplied, overloaded, ClaimsIdentity constructors. So you can use these base classes to benefit from the power of the claims approach in your own custom implementation, while still retaining the flexibility of selecting which ClaimsType values you want the legacy support to treat as names and roles:
public class MyClaimsIdentity : ClaimsIdentity
{
  public MyClaimsIdentity(IEnumerable<Claim> claims, string authenticationType) :
    base(claims, authenticationType, ClaimTypes.Email, ClaimTypes.GroupSid)
  {
  }
}
In the above example, accessing the Name property will search for a claim of type Email (often used as the unique user identifier), while a call to IsInRole will search for a match within the groups to which the user belongs.

Consuming the Principal

To get access to the claims from runtime code, we simply retrieve the IPrincipal from the thread as before, and cast it to a ClaimsPrincipal. Alternatively, and equivalently, we can obtain it in a single step via the handy, static ClaimsPrincipal.Current property:
var user = ClaimsPrincipal.Current;
With this we can now run arbitrary LINQ queries on the Claims collection property, or take advantage of the special FindFirst, FindAll and HasClaim methods, all of which accept either string parameters or lambdas.
var email = user.FindFirst(ClaimTypes.Email).Value;
var adClaims = user.FindAll(claim => claim.Issuer == "AD AUTHORITY");
Credential Types and Transformations

.NET 4.5 boasts out-of-the-box support, via descendants of the SecurityTokenHandler class, for a wide range of industry standard authentication protocols, which it translates automatically to ClaimsPrincipal format. These include:
  • Windows (SPNEGO, Kerberos, NTLMSSP)
  • Forms authentication
  • basic HTTP authentication
  • SSL client certificates
  • WS-Security tokens
  • SAML
Incoming credentials arrive, are processed and transformed, and then delivered to your application in the "final" ClaimsPrincipal. This processing begins with an appropriate SecurityTokenHandler deserializing and validating the incoming token, from which it constructs the "initial" ClaimsPrincipal. This can then be further processed or transformed, for example to validate the incoming claims or translate security-domain groups into application-domain user permissions, via your custom ClaimsAuthenticationManager (to run the following sample code, add references to System.IdentityModel and System.Security):
public class MyClaimsTransformer : ClaimsAuthenticationManager
{
  public override ClaimsPrincipal Authenticate(
    string resourceName, ClaimsPrincipal oldPrincipal)
  {
    // Validate the supplied claims
    var name = oldPrincipal.Identity.Name;
    if (string.IsNullOrWhiteSpace(name))
      throw new SecurityException("User name is missing!");
    // Process claims
    var creditLimit = LookupCreditLimit(name);
    var newClaims = new List<Claim>
    {
      new Claim(ClaimTypes.Name, name),
      new Claim("http://myclaims/creditlimit", creditLimit.ToString())
    };
    // Create & return the new ClaimsPrincipal
    var newId = new ClaimsIdentity(newClaims, "Local");
    return new ClaimsPrincipal(newId);
  }
}
Obviously we have to find somewhere to tell the system to use our new custom ClaimsAuthenticationManager class, and the equally obvious place to do that is in the configuration file. There's a new "system.identityModel" section:
<configuration>
  <configSections>
    <section
      name="system.identityModel"
      type="System.IdentityModel.Configuration.SystemIdentityModelSection" />
  </configSections>
  <system.identityModel>
    <identityConfiguration>
      <claimsAuthenticationManager
        type="MyNamespace.MyClaimsTransformer, MyAssemblyName" />
    </identityConfiguration>
  </system.identityModel>
</configuration>
Finally, at the point in our application code where previously we would perform our ad hoc claims processing, polluting our beautiful domain code with extraneous user security permissions logic, we now have essentially this fixed one-liner (no, don't laugh):
Thread.CurrentPrincipal =
  FederatedAuthentication
  .FederationConfiguration
  .IdentityConfiguration
  .ClaimsAuthenticationManager
  .Authenticate(
    "none", // Can pass eg a URL resource for context
    new WindowsPrincipal(WindowsIdentity.GetCurrent()));
Next time: session management.

No comments:

Post a Comment