Sponsored Links


Resources

.NET Research Library
Get .NET related white papers, case studies and webcasts

Building a Claims-Based Security Model in WCF Part 2Building a Claims-Based Security Model in WCF Part 2Building a Claims-Based Security Model in WCF Part 2 Discuss DiscussDiscuss Printer friendly Printer friendlyPrinter friendly
Building a Claims-Based Security Model in WCF Part 2

May 24, 2007

In Part 1 of this article I discussed the motivation behind implementing a claims-based security model for your WCF services and introduced a simple approach to normalizing claims for different credential types using a custom authorization policy. During the article, I discussed the following:

  • How different user credentials are mapped to a set of claims that are attached to the security context for each request.
  • How to access those claims through the AuthorizationContext to perform authorization, in lieu of role-based security checks.
  • How to create a custom authorization policy to issue a set of normalized claims for any credential, and attach those claims to the AuthorizationContext for later authorization checks.

Part 1 introduces some of the benefits of a claims-based security model. Specifically, that it enables service developers to focus on application-specific claims to authorize calls to each operation, while decoupling the authentication step and removing dependencies on specific credential types. This model also allows applications to support more than one credential type by mapping authenticated callers to a set of normalized claims.

In this article I will discuss other design decisions related to the claims-based security model. I’ll walk through the process for creating a set of claims-based utilities to encapsulate claims authorization at the service tier; and explain how SAML tokens can be issued by a security token service for authentication, supplying normalized claims to the security context.

Claims-Based Permission Demands

When you work with a traditional role-based security model, as I discussed in Part 1 of this article, you often apply role-based permission demands at the service operation to authorize calls. For services that may authorize both Windows and custom username and password credentials, you can stack permission demands as shown here – for each of the supported roles at an operation:

[PrincipalPermission(SecurityAction.Demand,
Role="BUILTIN\\Administrators")]
[PrincipalPermission(SecurityAction.Demand,
Role="Administrators")]
public string AdminOperation()
{
  // protected code
}

Permission demands implemented with the PrincipalPermission or PrincipalPermissionAttribute rely on the security principal attached to the executing thread to authorize calls. The most common check is to require specific roles through the IsInRole() function exposed by the IPrincipal type. This model for authorizing access to operations is easy to work with, and familiar, but an equivalent set of claims-based principal and principal permission types do not exist. In this section, I will describe a set of claims-based utilities I created to implement this familiar model around claims. It includes the following custom types:

  • ClaimsPrincipal
  • ClaimsPrincipalPermission
  • ClaimsPrincipalPermissionAttribute

ClaimsPrincipal

In Part 1 I showed you how to use a custom authorization policy to map a UserName credential to a set of application claims to be attached to the AuthorizationContext. Custom authorization policies are also responsible for providing a security principal for the request thread, in the form of an IPrincipal type. The custom authorization policy from Part 1 extracted the identity of the caller from the EvaluationContext, mapped that caller to a set of claims to be used for authorization, and created a simple GenericPrincipal with the identity and no roles. This work is done in the Evaluate() function of the custom authorization policy, as shown here:

public bool Evaluate(EvaluationContext evaluationContext, ref object state)
{
  if (evaluationContext.Properties.ContainsKey("Identities"))
  {
    List<IIdentity> identities =
evaluationContext.Properties["Identities"] as List<IIdentity>;
    IIdentity identity = identities[0];

    ClaimSet claims = MapClaims(identity);

    GenericPrincipal newPrincipal = new GenericPrincipal(identity, null);
    evaluationContext.Properties["Principal"] = newPrincipal;

    if (claims!=null)
      evaluationContext.AddClaimSet(this, claims);

    return true;
  }
  else
    return false;
}

Supplying a security principal is merely a formality in this case – since authorization is performed directly against the custom claim set added to the AuthorizationContext via AddClaimSet(). Assuming claims are assigned according to those discussed in Part 1, the following code looks for a claim set issued by http://www.thatindigogirl.com/samples/2006/06/issuer in the AuthorizationContext. In this example, the code checks that the claim set contains the “delete” claim for the “customers” resource:

public void DeleteCustomer(string customerId)
{
  AuthorizationContext authContext =
ServiceSecurityContext.Current.AuthorizationContext;

  ClaimSet issuerClaimSet = null;
  foreach (ClaimSet cs in authContext.ClaimSets)
  {
    Claim issuerClaim =
Claim.CreateNameClaim("http://www.thatindigogirl.com
/samples/2006/06/issuer");

    if (cs.Issuer.ContainsClaim(issuerClaim))
      issuerClaimSet=cs;
  }

  if (issuerClaimSet==null)
    throw new SecurityException("Access is denied. No claims were
provided from the expected issuer.");

  Claim c = new Claim("http://schemas.thatindigogirl.com
/samples/2006/06/identity/claims/delete",
"http://schemas.thatindigogirl.com/samples/2006/06/identity
/resources/customers", Rights.PossessProperty);

  if (!issuerClaimSet.ContainsClaim(c))
    throw new SecurityException("Access is denied. Required claims not
satisfied.");


  // delete customer
}

Implementing IClaimsPrincipal and ClaimsPrincipal

The first step in creating a first class claims-based model is to create a custom ClaimsPrincipal type for the request thread – to encapsulate the set of claims that authorization will be performed against. As you know, the security principal must implement IPrincipal which includes an Identity property and an IsInRole() method. To support the claims-based approach I created an IClaimsPrincipal interface which has the following definition:

public interface IClaimsPrincipal
{
  ClaimSet Claims {get;}
  ClaimSet Issuer {get;}

  bool HasRequiredClaims(ClaimSet requiredClaims);
}

As you would expect from a claims-based approach, a security principal implementing this interface provides access to the applicable claim set for authorization, the issuer of that claim set, and has a function to check that a set of required claims have been supplied. In my ClaimsPrincipal type I implement both IPrincipal and IClaimsPrincipal as shown in Listing 1.

Listing 1: Implementation of the ClaimsPrincipal type

using System;
using System.Collections.Generic;
using System.Text;
using System.Security.Principal;
using System.IdentityModel.Claims;

public class ClaimsPrincipal : IClaimsPrincipal, IPrincipal
{
  private ClaimSet m_claims;
  private IIdentity m_identity;

  public ClaimsPrincipal(IIdentity identity, ClaimSet claims)
  {
    this.m_identity = identity;
    this.m_claims = claims;
  }

  ClaimSet IClaimsPrincipal.Issuer
  {
    get { return this.m_claims.Issuer; }
  }

  bool IClaimsPrincipal.HasRequiredClaims(ClaimSet requiredClaims)
  {
    bool hasClaims = true;
    bool issuerMatch = false;

    // check issuer
    foreach (Claim c in requiredClaims.Issuer)
    {
      if (this.m_claims.Issuer.ContainsClaim(c))
      {
        issuerMatch=true;
        break;
      }
    }

    // check required claims
    if (issuerMatch)
    {
      foreach (Claim c in requiredClaims)
      {
        if (!this.m_claims.ContainsClaim(c))
        {
          hasClaims = false;
          break;
        }
      }
    }

    return issuerMatch && hasClaims;
  }

  ClaimSet IClaimsPrincipal.Claims
  {
    get { return this.m_claims; }
  }

  IIdentity IPrincipal.Identity
  {
    get
    {
      return this.m_identity;
    }
  }

  bool IPrincipal.IsInRole(string role)
  {
    throw new NotSupportedException("ClaimsPrincipal does not
implement role-based security checks.");
  }

}

The constructor takes an IIdentity type and a ClaimSet representative of the claims to authorize. In the implementation of IPrincipal, the Identity property returns this identity reference, but the IsInRole() function throws an exception since we’re not expecting any role-based checks when using claims-based security. The implementation of IClaimsPrincipal returns the associated ClaimSet from the Claims property, returns the issuer’s ClaimSet for the Issuer property, and performs a check for the correct issuer and claim set in the HasRequiredClaims() method.

Attaching the ClaimsPrincipal Instance

This ClaimsPrincipal type can be constructed in the custom authorization policy instead of the GenericPrincipal shown earlier. The same claim set that is being added to the EvaluationContext should also be passed to the ClaimsPrincipal when it is constructed, as shown here in this updated Evaluate() method:

public bool Evaluate(EvaluationContext evaluationContext,
ref object state)
{

  if (evaluationContext.Properties.ContainsKey("Identities"))
  {
    List<IIdentity> identities =
evaluationContext.Properties["Identities"] as List<IIdentity>;
    IIdentity identity = identities[0];

    ClaimSet claims = MapClaims(identity);

    ClaimsPrincipal newPrincipal =
new ClaimsPrincipal(identity, claims);
    evaluationContext.Properties["Principal"] = newPrincipal;

    evaluationContext.AddClaimSet(this, claims);

    return true;
  }
  else
    return false;
}

Validating ClaimsPrincipal Claims

This removes the need to traverse the AuthorizationContext for the correct issuer. Instead, you can retrieve the security principal attached to the thread, check that it implements IClaimsPrincipal, and validate that the required claims were provided:

IClaimsPrincipal claimsPrincipal = Thread.CurrentPrincipal
as IClaimsPrincipal;

if (claimsPrincipal == null)
  throw new SecurityException("Access is denied. Security principal
should be a IClaimsPrincipal type.");

Claim issuerName = Claim.CreateNameClaim(
ClaimsAuthorizationPolicy.IssuerName);
List<Claim> issuerClaims = new List<Claim>();
issuerClaims.Add(issuerName);

List<Claim> requiredClaims = new List<Claim>();
requiredClaims.Add(new Claim(�http://schemas.thatindigogirl.com
/samples/2006/06/identity/claims/delete�,
�http://schemas.thatindigogirl.com/samples/2006/06/identity
/resources/customers�, Rights.PossessProperty));

DefaultClaimSet claimSet = new DefaultClaimSet(
new DefaultClaimSet(issuerClaims), requiredClaims);

if (!claimsPrincipal.HasRequiredClaims(claimSet))
  throw new SecurityException("Access is denied. Security principal
does not satisfy required claims.");

HasRequiredClaims() expects you to pass a ClaimSet that includes the required claims with the correct issuer. Clearly, this still adds bloat to your code, but it can be simplified by using the ClaimsPrincipalPermission type that I’ll explain next.

ClaimsPrincipalPermission

You can perform permission demands in code to enforce role-based security using the following syntax:

PrincipalPermission p = new PrincipalPermission(null,
"Administrators", true);
p.Demand();

The PrincipalPermission type is passed a username, role and authentication status. When the permission demand is invoked the security principal attached to the request thread is checked against these values – in this case the user name is null, thus not checked, but the user must be in the “Administrators” role and be authenticated. I created a custom ClaimsPrincipalPermission to facilitate similar demands for claims.

Like with the PrincipalPermission type, the ClaimsPrincipalPermission implements IPermission, ISecurityEncodable and IUnrestrictedPermission. These interfaces are shown here:

public interface IPermission : ISecurityEncodable
{
  void Demand();

  IPermission Copy();
  IPermission Intersect(IPermission target);
  bool IsSubsetOf(IPermission target);
  IPermission Union(IPermission target);
}

public interface ISecurityEncodable
{
  void FromXml(SecurityElement e);
  SecurityElement ToXml();
}

public interface IUnrestrictedPermission
{
  bool IsUnrestricted();
}

In the following paragraphs I will discuss the implementation of the ClaimsPrincipalPermission type, including each of these methods that are common to all IPrincipal types.

Constructing ClaimPrincipalPermission

The implementation of Demand() is where all the work is done to check the security principal attached to the current thread and ensure that the requirements held by the ClaimPrincipalPermission are met. That implies that the ClaimsPrincipalPermission must supply overloaded constructors to provide information for the permission demand. ClaimsPrincipalPermission issues demands based on the following properties:

  • IsAuthenticated: Since ClaimsPrincipal still carries information about the identity of the caller, you can validate that the caller has been authenticated by providing a value for this property.
  • RequiredClaims: This is a ClaimSet which contains the list of required claims to be demanded, and information about the issuer of those claims.
  • Issuer: This is a ClaimSet identifying the issuer of the required claims.

The ClaimsPrincipalPermission constructor takes a boolean and ClaimSet to populate these properties as follows:

// fields
private ClaimSet m_requiredClaims;
private bool m_isAuthenticated;

// properties
public bool IsAuthenticated
{
  get
  {
    return m_isAuthenticated;
  }
}

public ClaimSet Issuer
{
  get
  {
    return ((this.m_requiredClaims==null)?null :
this.m_requiredClaims.Issuer);
  }
}

public ClaimSet RequiredClaims
{
  get
  {
    return this.m_requiredClaims;
  }
}

// constructor
public ClaimsPrincipalPermission(bool isAuthenticated,
ClaimSet requiredClaims)
{
  this.m_isAuthenticated = isAuthenticated;
  this.m_requiredClaims=requiredClaims;
}

Once you have constructed the ClaimsPrincipalPermission type, you can use it to perform permission demands.

Another constructor is also supplied allowing the ClaimsPrincipalPermission to be initialized as restricted or unrestricted, according to the PermissionState enumeration.

private bool m_isUnrestricted;

public ClaimsPrincipalPermission(PermissionState state)
{
  this.m_isUnrestricted = (state == PermissionState.Unrestricted);
}

This value returned by the IsUnrestricted property of the IUnrestrictedPermission interface.

Demand()

Permission demands use the properties of the IPermission type to check the security principal attached to the thread for specified requirements. The implementation of Demand() for ClaimsPrincipalPermission executes the following checks:

  • It checks to see that the security principal attached to the thread is an IClaimsPrincipal type, and throws a SecurityException if it is not.
  • If the IsAuthenticated property is set to true, it checks to see that the current principal is authenticated.
  • If the RequiredClaims property is null, it does not check any further, and the demand is complete, otherwise, the requirements for issuer and claims are verified as discussed next.
  • The Issuer is checked next. When the RequiredClaims property is set, a ClaimSet is provided which includes a ClaimSet for the Issuer. The assumption here is that the developer has provided one or more identifying claims about the required Issuer. If one of these claims is matched by the Issuer of the security principal’s claims, then we have a match.
  • Once an Issuer match is verified the list of required claims are checked. All required claims must be present in the RequiredClaims ClaimSet, or the demand fails.

The code to execute these checks begins with the Demand() implementation, which then calls to a shared CheckClaims() method, which then calls to the ClaimsPrincipal’s HasRequiredClaims() method (shown in Listing 1). The Demand() and CheckClaims() methods are shown in Listing 2.

Listing 2: Demand() and CheckClaims() functions from ClaimsPrincipalPermission

public void Demand()
{
  IClaimsPrincipal p = Thread.CurrentPrincipal as IClaimsPrincipal;

  if (p == null)
    throw new SecurityException("Access is denied. Security principal
should be a IClaimSetPrincipal type.");

  this.CheckClaims(p);
}

public void CheckClaims(IClaimsPrincipal principal)
{
  IPrincipal p = principal as IPrincipal;

  if (this.m_isAuthenticated && !p.Identity.IsAuthenticated)
  {
    throw new SecurityException("Access is denied. Security principal
is not authenticated.");
  }

  if (this.m_requiredClaims==null)
  {
    return;
  }

  if (!principal.HasRequiredClaims(this.m_requiredClaims))
    throw new SecurityException("Access is denied. Security principal
does not satisfy required claims.");

}

Now I will discuss other members of IPermission implemented by ClaimsPrincipalPermission – specifically Copy(), Intersect(), IsSubsetOf() and Union() – used to facilitate the generation of new permission objects or permission comparison.

Copy()

Copy() is the simplest IPermission function. It requires only that you clone the current permission object as shown here:

public IPermission Copy()
{
  if (this.IsUnrestricted())
    return new
ClaimsPrincipalPermission(PermissionState.Unrestricted);
  else
    return new ClaimsPrincipalPermission(this.m_isAuthenticated,
this.m_requiredClaims);
}

If the permission was constructed as unrestricted, values for IsAuthenticated and RequiredClaims take on their defaults.

Intersect()

Intersect() returns a new permission object that represents the intersection of two permissions. In the ClaimsPrincipalPermission, this is implemented in the following manner:

  • If the permission to intersect with is null, null is returned.
  • If the permission to intersect with is not a ClaimsPrincipalPermission type, null is returned.
  • If either the permission to intersect or this permission is unrestricted, return a copy of the opposite permission.
  • If IsAuthenticated does not match for both permissions, null is returned.
  • If ALL of the Issuer claims are not matched, null is returned. The assumption here is that a developer is providing another permission to intersect with, and they should thus describe the Issuer with the same set of claims. This simplifies how developers can use these permissions, by reducing the complexity that would follow if you attempted to intersect the ClaimSet of an Issuer, and the Issuer’s Issuer, and that Issuer’s Issuer, and so on. In other words, the nature of the ClaimSet and the nested Issuer claim sets demands simplicity here.
  • Once the Issuer ClaimSet is verified as a perfect match between permissions, the actual claim sets can be intersected. So long as they are issued by the same Issuer, the list of required claim sets in both permissions can be intersected to produce a new ClaimSet by the same Issuer.

The resulting code is shown in Listing 3.

Listing 3: Implementation of Intersect() and IsExactIssuerMatch()

public IPermission Intersect(IPermission target)
{
  if (target == null)
    return null;

  ClaimsPrincipalPermission perm = target as
ClaimsPrincipalPermission;
  if (perm == null)
    return null;

  if (this.IsUnrestricted())
    return target.Copy();

  if (perm.IsUnrestricted())
    return this.Copy();

  if (this.m_isAuthenticated != perm.IsAuthenticated)
    return null;

  if (!IsExactIssuerMatch(perm.Issuer)) return null;

  List claims = new List();
  foreach (Claim c in this.m_requiredClaims)
  {
    if (perm.RequiredClaims.ContainsClaim(c))
    {
      claims.Add(c);
    }
  }

  // it is assumed that the issuers are identical from the call
  // to IsExactIssuerMatch() above
  ClaimsPrincipalPermission newPerm = new
ClaimsPrincipalPermission(this.m_isAuthenticated, new
DefaultClaimSet(this.m_requiredClaims.Issuer, claims));
  return newPerm;

}

protected bool IsExactIssuerMatch(ClaimSet target)
{
  bool issuerMatch = true;
  foreach (Claim c in target)
  {
    if (!this.m_requiredClaims.Issuer.ContainsClaim(c))
    {
      issuerMatch = false;
      break;
    }
  }
  return issuerMatch;
}

IsSubsetOf()

IsSubsetOf() returns a boolean result after checking to see if the current permission is a subset of the permission passed to the method. This method does the following:

  • Returns false if the permission passed is null.
  • Returns false if the permission passed is not a ClaimsPrincipalPermission type.
  • Returns true if the target permission is unrestricted. Returns false if this permission is unrestricted.
  • Returns false if the IsAuthenticted properties do not match.
  • Returns false if the Issuer of both permissions are not an exact match.
  • Finally, it checks to see if the required claims in this permission instance are all found in the permission passed to the method. If they are not all present, returns false, otherwise true.

The resulting code is shown here:

public bool IsSubsetOf(IPermission target)
{

  if (target == null)
    return false;

  ClaimsPrincipalPermission perm = target as
ClaimsPrincipalPermission;
  if (perm == null)
    return false;

  if (perm.IsUnrestricted)
    return true;

  if (this.IsUnrestricted)
    return false;

  if (this.m_isAuthenticated != perm.IsAuthenticated)
    return false;

  if (!IsExactIssuerMatch(perm.Issuer)) return false;

  bool isSubsetOf = false;
  foreach (Claim c in this.m_requiredClaims)
  {
    if (!perm.RequiredClaims.ContainsClaim(c))
    {
      isSubsetOf=false;
      break;
    }

  }

  return isSubsetOf;
}

Union ()

Union() returns a new permission type that includes all of the required claims of two permissions, assuming that they both share the same setting for IsAuthenticated and Issuer values. Similar validation on the permission passed to the method, the IsAuthenticated property and the Issuer value are executed before creating a new permission type with the entire set of claims (without duplicates). The code looks as follows:

public IPermission Union(IPermission target)
{
  if (target == null)
    return null;

  ClaimsPrincipalPermission perm = target as
ClaimsPrincipalPermission;
  if (perm == null)
    return null;

  if (perm.IsUnrestricted|| this.IsUnrestricted)
    return new
ClaimsPrincipalPermission(PermissionState.Unrestricted);

  if (this.m_isAuthenticated != perm.IsAuthenticated)
    return null;

  if (!IsExactIssuerMatch(perm.Issuer)) return null;

  List<Claim> claims = new List<Claim>();
  foreach(Claim c in this.m_requiredClaims)
    claims.Add(c);

  foreach (Claim c in perm.RequiredClaims)
  {
    if (!this.m_requiredClaims.ContainsClaim(c))
    {
      claims.Add(c);
    }
  }

  // it is assumed that the issuers are identical from the call
  // to IsExactIssuerMatch() above
  ClaimsPrincipalPermission newPerm = new
ClaimsPrincipalPermission(this.m_isAuthenticated, new
DefaultClaimSet(this.m_requiredClaims.Issuer, claims));
  return newPerm;

}

FromXml() and ToXml()

IPermission inherits ISecurityEncodable as I mentioned earlier. The job of FromXml() and ToXml() are to support XML serialization and deserialization of the permission. This is not a necessary feature for this particular permission type, thus is not implemented.

Claims-Based Demands

With this ClaimsPrincipalPermission you can now place permission demands in code, for example in each service operation, as follows:

public void DeleteCustomer(string customerId)
{

  ClaimSet issuerClaimSet = new
DefaultClaimSet(Claim.CreateNameClaim(
"http://www.thatindigogirl.com/samples/2006/06/issuer"));

  Claim claim = new Claim(
"http://schemas.thatindigogirl.com/samples/2006/06/identity/
claims/delete", "http://schemas.thatindigogirl.com/samples/2006/06/
identity/resources/customers", Rights.PossessProperty);

  ClaimSet requiredClaimSet = new
DefaultClaimSet(issuerClaimSet, claim);

  ClaimsPrincipalPermission perm = new
ClaimsPrincipalPermission(true, requiredClaimSet);

  perm.Demand();

  // code to delete customer
}

But wait – isn’t this supposed to reduce the amount of code necessary to check claims? The problem is that it isn’t as simple as passing a string array of roles, when we do claims-based security. Generating a ClaimSet is the only reliable way to initialize the ClaimsPrincipalPermission with the issuer and required claims for the demand. But, since claims are tightly related to the authorization policy at your service, you can, and should, provide utility functions to generate claim sets for the issuer, and for common claims.

If you do, you can reduce the above code to these two lines:

ClaimsPrincipalPermission perm = new ClaimsPrincipalPermission(true,
ClaimsAuthorizationPolicy.CreateCustomersClaimSet(
ClaimsAuthorizationPolicy.ClaimTypes.Delete));
perm.Demand();

The CreateCustomersClaimSet() function is a static function exposed by the custom IAuthorizationPolicy type, ClaimsAuthorizationPolicy. Inside, it leverages string constants defining ClaimTypes and Resources as discussed in Part 1 of this article, to create a ClaimSet using the authorization policy issuer. It allows you to pass a parameter array of strings indicating the ClaimTypes to be included in the ClaimSet. Internal validation ensures that strings passed to the method are actually valid ClaimTypes. The core code is shown here:

public static ClaimSet CreateApplicationClaimSet(
params string[] claimTypes)
{
  List<Claim> claims = new List<Claim>();
  foreach(string s in claimTypes)
  {
    if (!IsValidClaimType(s))
      throw new SecurityException(string.Format("Invalid claim type
provided: {0}", s));

    claims.Add(new Claim(s,
ClaimsAuthorizationPolicy.Resources.Application,
Rights.PossessProperty));
  }

  return new DefaultClaimSet(
ClaimsAuthorizationPolicy.CreateIssuerClaimSet(), claims);
}

public static ClaimSet CreateIssuerClaimSet()
{
  return new DefaultClaimSet(Claim.CreateNameClaim(
ClaimsAuthorizationPolicy.IssuerName));
}

NOTE: You can look at the code sample for this article for a similar example with a complete listing of constants and helper methods for the authorization policy.

ClaimsPrincipalPermissionAttribute

As I mentioned earlier, you can also perform permission demands declaratively using the PrincipalPermissionAttribute as follows:

[PrincipalPermission(SecurityAction.Demand,
Role="BUILTIN\\Administrators")]
[PrincipalPermission(SecurityAction.Demand,
Role="Administrators")]
public string AdminOperation()
{
  // protected code
}

Like the PrincipalPermission, the PrincipalPermissionAttribute type can be initialized with a username, role and authentication status. You also indicate you are executing a demand with the first parameter, SecurityAction.Demand. To execute the demand, the runtime asks the attribute to create an instance of the associated permission, PrincipalPermission, and its Demand() function is executed to enforce this before the operation can be invoked.

To provide a similar experience for claims-based demands, I have created a ClaimsPrincipalPermissionAttribute which I’ll describe in the next sections.

Implementing CodeAccessSecurityAttribute

As with the PrincipalPermissionAttribute type, the ClaimsPrincipalPermissionAttribute inherits CodeAccessSecurityAttribute, which in turn inherits SecurityAttribute. The base type, SecurityAttribute, supplies these key members:

  • Action: This property indicates the SecurityAction to execute, which in this context is SecurityAction.Demand.
  • Unrestricted: If this property is true it indicates that the caller should be granted full access to the resource protected by the associated permission, if false, no access is granted.
  • CreatePermission(): Invoked by the runtime to construct the permission type associated with the CodeAccessSecurityAttribute, in order to execute the permission demand.

The idea is to provide appropriate public properties on this custom attribute to so that the associated permission type can be constructed with values that can be used for permission demands. The base type for the attribute, CodeAccessSecurityAttribute, provides a constructor that takes the PermissionState enumeration setting the restricted state of the attribute, if applicable. The implementation of ClaimsPrincipalPermissionAttribute also exposes two other properties: Authenticated and RequiredClaimType.

The Authenticated property merely indicates if the security principal attached to the thread must be authenticated, just like the ClaimsPrincipalPermission. RequiredClaimType is a string property that indicates the claim type represented by the attribute. Ultimately this will be used to construct a ClaimSet to be passed to the ClaimsPrincipalPermission for permission demands. In this implementation I am assuming that the caller will pass a valid claim type from the ClaimsAuthorizationPolicy.ClaimTypes static class, discussed in Part 1. Resource is also a string property indicating the resource associated with the claim. This should be based on the ClaimsAuthorizationPolicy.Resources static class, also discussed in Part 1. Based on these supported resources and claim types I am inferring the issuer when the claim set is created during CreatePermission().

Listing 4 shows the full implementation.

Listing 4: Implementation of ClaimsPrincipalPermissionAttribute

using System;
using System.Security.Permissions;
using System.IdentityModel.Claims;

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class ClaimsPrincipalPermissionAttribute :
CodeAccessSecurityAttribute
{

  private bool m_isAuthenticated;
  private string m_requiredClaimType;
  private string m_resource;

  public ClaimsPrincipalPermissionAttribute(SecurityAction action)
: base(action)
  {
    this.m_isAuthenticated = true;
  }

  public string RequiredClaimType
  {
    get
    {
      return this.m_requiredClaimType;

    }
    set
    {
      this.m_requiredClaimType = value;
    }
  }

  public string Resource
  {
    get
    {
      return this.m_resource;
    }
    set
    {
      this.m_resource = value;
    }
  }

  public bool Authenticated
  {
    get
    {
      return m_isAuthenticated;
    }
    set
    {
      m_isAuthenticated = value;
    }
  }

  public override System.Security.IPermission CreatePermission()
  {
    if (this.Unrestricted)
      return new
ClaimsPrincipalPermission(PermissionState.Unrestricted);

  ClaimSet cs = ClaimsAuthorizationPolicy.CreateClaimSet
(this.m_resource, this.m_requiredClaimType);
  return new ClaimsPrincipalPermission(this.m_isAuthenticated, cs);

  }
}

Attribute Usage

All attributes have an AttributeUsageAttribute applied to their type definition to indicate allowed use of the attribute. In the case of the ClaimsPrincipalPermissionAttribute, the following AttributeUsageAttribute is applied (also shown in Listing 4):

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true, Inherited = false)]
public class ClaimsPrincipalPermissionAttribute : CodeAccessSecurityAttribute

In this case, the attribute is restricted to methods on a class, and cannot be inherited if there is a base type. So that you can stack many ClaimsPrincipalPermissionAttribute on a single method, AllowMultiple is set to true.

Declarative Claims-Based Demands

The purpose of providing the ClaimsPrincipalPermissionAttribute is to support declarative permission demands for each service operation instead of constructing the ClaimsPrincipalPermission directly. The following illustrates issuing a permission demand for the right to delete customers:

[ClaimsPrincipalPermissionAttribute(SecurityAction.Demand,
RequiredClaimType = ClaimsAuthorizationPolicy.ClaimTypes.Delete, Resource=ClaimsAuthorizationPolicy.Resources.Customers)]
public void DeleteCustomer(string customerId)
{
  // protected code
}

The verbosity of the attribute declaration results from the nature of claims-based security – it isn’t as simple as just passing a list of roles or user names to the attribute or permission. I have already simplified it by inferring the issuer, assuming it is associated with the type of claims and resources supported by the authorization policy. But, you can take it one level further by defining custom attributes that derive from this more general attribute. For example, I could create an attribute specifically for Create, Read, Update and Delete rights for Customers. Consider the following DeleteCustomersClaimAttribute:

[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class,
AllowMultiple = true, Inherited = false)]
public class DeleteCustomersClaimAttribute:
ClaimsPrincipalPermissionAttribute
{

  public DeleteCustomersClaimAttribute(SecurityAction action)
: base(action)
  {
    this.Authenticated=true;
    this.RequiredClaimType =
ClaimsAuthorizationPolicy.ClaimTypes.Delete;
    this.Resource=ClaimsAuthorizationPolicy.Resources.Customers;
  }
}

When applied to a service operation, it greatly simplifies the declaration:

[DeleteCustomersClaim(SecurityAction.Demand)]
public void DeleteCustomer(string customerId)
{
  // protected code
}

So, when you design your claims-based permission model, you can come up with a collection of simplified attributes for developers to work with, while encapsulating the richness of the claims-based authorization within.

Pros and Cons of Claims-Based Security Permissions

The nice thing about using this claims-based security model is that developers can focus on demanding specific claims before operations can be executed, but they don’t need to know what form of authentication was used to grant those claims, so long as they trust the issuer. Since the model is familiar, the task is simplified. Furthermore, since the ClaimsPrincipalPermission also holds the ClaimSet granted by the trusted issuer, permission demands can also be placed in downstream assemblies used by the service, if applicable. This may be necessary when business rules require knowledge of the authenticated caller, or their granted rights.

One downside of this approach is that the authorization policy must supply a complete set of claims for the authorized caller, so that operations can demand the claims they require. If there is a long list of claims that apply to features, actions and other information supplied about the caller, the ClaimSet can become quite large. A refinement of this model would be to modify the functionality of the authorization policy to grant a claim set that merely identifies the caller, without assigning all possible claims. Then, modify the attribute to gather the claims for a specific service and/or operation, on demand. Of course this can add overhead to each operation call, but can reduce the size of the claim set stored by the security context that may also need to be serialized and forward to downstream services.

Security Token Services, SAML Tokens and Claims

So far I have focused on how you can build a custom authorization policy to map any type of credential to a normalized set of claims – enabling service developers to authorize calls based on those claims regardless of the way calls are authenticated. Assuming that a trusted set of claims has been added to the security context, the claims-based authorization model I have described can be applied to your service operations, programmatically or declaratively with ClaimsPrincipalPermission and ClaimsPrincipalPermissionAttribute, respectively. What these constructs rely on is that a ClaimsPrincipal has been attached to the thread carrying the claim set that should be used for authorization. Figure 1 illustrates how the custom authorization policy attaches a set of claims and a custom security principal to the security context for each call to a business service.

Figure 1: This custom authorization policy maps a UserName credential to a set of normalized claims.

Using a custom authorization policy to map credentials to normalized claims is a great way to start moving to a claims-based security model, without introducing the concepts of federation and SAML tokens. So long as your services authorize calls based on claims attached to the security context or ClaimsPrincipal you can introduce federation without modifications to service code. In this section I’ll explain how federated security and SAML tokens can be introduced while still utilizing the claims-based security model I’ve described.

Token Issuance

Figure 1 illustrates a scenario where the business service is responsible for configuring a custom authorization policy to normalize claims. Fortunately the authorization policy decouples authentication of the UserName token (or, other tokens if supported) from the authorization against normalized claims. The requirement is only that the AuthorizationContext contains an appropriate claim set from the desired issuer, and that a ClaimsPrincipal is attached to the thread also referencing that claim set.

You can use WSFederationHttpBinding to delegate authentication to a Security Token Service (STS) that will issue a SAML token containing the same normalized claims. Using this binding the client will authenticate to the STS, retrieve the SAML token with normalized claims, and then authenticate to the business service with that token. The resulting claim set is automatically attached to the security context for authorization purposes, as I will discuss shortly. Figure 2 illustrates the flow of token issuance.

Figure 2: With WSFederationHttpBinding clients authenticate first to the STS, and then present issued tokens (such as SAML) to services.

WSFederationHttpBinding

You can configure your services to require a SAML token issued by a particular STS using WSFederationHttpBinding. This binding requires you to provide the following information:

  • IssuedTokenType: The type of token you require the STS to issue. In this case it is a SAML v1.1 token.
  • ClaimTypeRequirements: A list of required or optional claim types that should be included in the issued token. In the case of the SAML token these will be supplied as SAML attributes. The service model will be able to automatically unpack those attributes into a claim set.
  • Issuer Address: The Uri where the WS-Trust endpoint implemented by the STS can be located.
  • Issuer Binding: The binding requirements of the issuer, so that the client can supply the correct binding with credentials to authenticate to the STS.
  • Issuer Identity: In this case a certificate reference is used to identify the STS.

The resulting WSFederationHttpBinding section is shown in Listing 5.

Listing 5: WSFederationHttpBinding configuration.

<wsFederationHttpBinding>
  <binding name="wsFed" >
    <security mode="Message">
      <message issuedTokenType="http://docs.oasis-open.org/wss/oasis-
wss-saml-token-profile-1.1#SAMLV1.1"
negotiateServiceCredential="false">

         <claimTypeRequirements>
          <add
claimType="http://schemas.thatindigogirl.com/samples/2006/06/identity/
claims/create" isOptional="true"/>

          <add
claimType="http://schemas.thatindigogirl.com/samples/2006/06/identity/
claims/read" isOptional="false" />
          <add
claimType="http://schemas.thatindigogirl.com/samples/2006/06/identity/
claims/update" isOptional="true"/>
          <add
claimType="http://schemas.thatindigogirl.com/samples/2006/06/identity/
claims/delete" isOptional="true"/>
        </claimTypeRequirements>

        <issuer address="http://localhost:51213/TokenIssuer/Service.svc"
binding ="wsHttpBinding"  bindingConfiguration="stsBinding" >
          <identity>
	      <certificateReference findValue="IPKey"
x509FindType="FindBySubjectName" storeLocation="LocalMachine"
storeName="TrustedPeople" />
          </identity>
        </issuer>

      </message>
    </security>
  </binding>
</wsFederationHttpBinding>

Clients use a similar configuration to initialize a proxy to invoke the service, thus the proxy has enough information to automatically make calls to the STS passing the UserName credential to authenticate. The goal of this process is that the SAML token returned by the STS will contain the normalized claims that were previously issued by the ClaimsAuthorizationPolicy discussed earlier.

Normalizing Claims at the STS

When you delegate authentication to an STS, this implies that the STS knows how to generate the required claims to authorize access at your business services. An STS can be any service that implements the WS-Trust contract as described by OASIS (see resources). Normally, you will not consider implementing your own WS-Trust endpoint. Instead, you might use a pre-existing STS such as the next generation Active Directory Federation Services (ADFS) or Ping Trust. To illustrate a simple example of an STS, I have provided a custom STS in the code sample that will issue a SAML token with the normalized claims already discussed.

This custom STS consists of a WCF service that authenticates callers with UserName token, and maps those tokens to normalized claims. In fact the configuration of this custom STS service can use the same custom authorization policy to generate a claim set for the UserName. As Figure 3 illustrates, the authorization policy provides a claim set to the security context. This claim set is then used by the STS to generate a SAML token with the appropriate claims.

Figure 3: The custom STS provided in the sample code uses the same custom authorization policy to map UserName to normalized claims.

From a high level, the SAML token includes a private key signature indicating issuer, which in this case is a private key using the DNS “IPKey”. The token also includes a collection of SAML attributes which each evaluate to the assigned normalized claims. Listing 6 illustrates the main piece of code where the SAML token is actually generated, with the generation of the attribute list and token generation shown bold. The call to GetClaimSet() checks the authorization context for claims added by the custom authorization policy discussed earlier, returning that set of claims.

Listing 6: Generating a SAML token from normalized claims.

private SecurityToken CreateSAMLToken(DateTime validFrom,
DateTime validTo, SecurityKey signingKey,
SecurityKeyIdentifier signingKeyIdentifier,
SecurityKeyIdentifier proofKeyIdentifier,
IList<ClaimTypeRequirement> claimReqs )
{
  List<string> confirmations = new List<string>();
  confirmations.Add(SamlConstants.HolderOfKey);

  SamlSubject subject = new SamlSubject(null, null, m_issuer,
confirmations, null, proofKeyIdentifier);

  List<SamlAttribute> attributes = new List<SamlAttribute>();
  ClaimSet cs = GetClaimSet(claimReqs);

  foreach (Claim c in cs)
    attributes.Add(new SamlAttribute(c));

  List<SamlStatement> statements = new List<SamlStatement>();
  statements.Add(new SamlAttributeStatement(subject, attributes));

  SamlConditions conditions = new SamlConditions(validFrom, validTo);

  SamlAssertion assertion = new SamlAssertion("_" + Guid.NewGuid().ToString(), m_issuer, validFrom, conditions, null, statements);

  string signatureAlgorithm = GetSignatureAlgorithm(signingKey);
  assertion.SigningCredentials = new SigningCredentials(signingKey,
signatureAlgorithm, SecurityAlgorithms.Sha1Digest,
signingKeyIdentifier);

  SamlSecurityToken token = new SamlSecurityToken(assertion);
  return token;
}

Without getting caught up in the details of the SAML token, or the STS, the point here is that any STS can be configured to issue SAML tokens that include attributes representative of a set of normalized claims your services depend on. You don’t have to write your own STS to do it. With this arrangement you can delegate authentication of callers so that they can pass any supported credential to the STS, and allow a trusted STS to determine the claims to be granted. Figure 4 illustrates an STS that is capable of authenticating Windows, UserName, Certificate, SAML (from a different source), or a token from CardSpace (self-issued, perhaps).

Figure 4: An STS can authenticate any type of credential and map that to normalized claims encapsulated by a SAML token.

By signing the token with the STS’ private key the claims passed inside the SAML token are guaranteed to have come from a trusted source – assuming that the business service “trusts” that signature, which I’ll discuss next.

Generating Claims from SAML Tokens

When a service is configured to require a SAML token (in this case using WSFederationHttpBinding) the token is authenticated at the service before its attributes are extracted – a ClaimSet constructed from them. When a SAML token is supplied in the security headers for a message, the SamlSecurityTokenAuthenticator type validates the signature of the token. By default, it is expected that the token is signed by a trusted RSA issuer, and this information is supplied by a service behavior as shown in the <issuedTokenAuthentication> section of Listing 7.

Listing 7: Service behavior configuration to supply a custom authorization policy and indicate trusted RSA issuers.

<serviceBehaviors>
  <behavior name="serviceBehavior">
    <serviceAuthorization principalPermissionMode="Custom" >
      <authorizationPolicies>
        <add policyType=
"ClaimsBasedSecurityComponents.SAMLClaimsAuthorizationPolicy,
ClaimsBasedSecurityComponents"/>
      </authorizationPolicies>
    </serviceAuthorization>
    <serviceCredentials>
      <issuedTokenAuthentication allowUntrustedRsaIssuers="false">
	  <knownCertificates>
	      <add findValue="IPKey" storeLocation ="LocalMachine"
storeName="TrustedPeople" x509FindType ="FindBySubjectName"/>
        </knownCertificates>
      </issuedTokenAuthentication>
      <serviceCertificate findValue="RPKey"
storeLocation="LocalMachine" storeName="My"
x509FindType="FindBySubjectName"/>
    </serviceCredentials>
  </behavior>
</serviceBehaviors>

In this case, the STS signed the SAML token with its private key, IPKey. Since this key is trusted the SAML token attributes are extracted and placed in the AuthorizationContext in the form of a ClaimSet, as illustrated by Figure 5.

Figure 5: Trusted SAML tokens are converted to claim sets for the authorization context.

Since the ClaimsPrincipalPermission and ClaimsPrincipalPermissionAttribute discussed in this article depend on the security principal attached to the thread to be a ClaimsPrincipal, a custom authorization policy (also shown configured in Listing 6) is still necessary to create the custom principal and associate the correct claim set. To handle this I provided a SAMLClaimsAuthorizationPolicy, which inherits ClaimsAuthorizationPolicy and overrides Evaluate() as follows:

public override bool Evaluate(EvaluationContext evaluationContext, ref
object state)
{
  ClaimSet principalClaimSet = null;

  foreach (ClaimSet cs in evaluationContext.ClaimSets)
  {
    if (cs.Issuer.ContainsClaim(Claim.CreateDnsClaim("IPKey")))
    {
      principalClaimSet = cs;
    }
  }

  if (principalClaimSet != null)
  {
    ClaimsPrincipal newPrincipal = new ClaimsPrincipal(new
GenericIdentity("IPKey"), principalClaimSet);
    evaluationContext.Properties["Principal"] = newPrincipal;
    return true;
  }
  else
    return false;
}

When this authorization policy is invoked the SAML token has already been validated, its claims attached to the EvaluationContext. The issuer of the claim set we want is IPKey, in this case. Thus, the claims issued by IPKey, the STS, are attached to the ClaimsPrincipal instance. Figure 6 illustrates this addition.

Figure 6: A custom authorization policy that attaches claims received through a SAML token to a ClaimsPrincipal.

At this point, the SAML token has carried the normalized claims to the business service, and the business service has constructed a custom ClaimsPrincipal to associate the claim set to the security principal to support the claims-based authorization model I described earlier.

Adjustments to the Claims-Based Authorization Model

A few adjustments must be made to the model I have described thus far, to support the claim set extracted from the SAML token. If you recall, the original ClaimsAuthorizationPolicy constructs a normalized claim set using a name claim as the Issuer, where the name is “http://www.thatindigogirl.com/samples/2006/06/issuer”. When claims are extracted from a SAML token, the Issuer is described by an X509ClaimSet, in this case IPKey. That means there is a Name claim and DNS claim of “IPKey”, and the original issuer name is nowhere to be found.

To support claim sets issued by the ClaimsAuthorizationPolicy, or via SAML token, I added additional claims to the Issuer claim set that include the following information:

public const string IssuerUri =
"http://www.thatindigogirl.com/samples/2006/06/issuer";

public const string IssuerName = "IPKey";

The helper function used to generate the Issuer claim set now generates a Uri, Name and DNS claim as shown here:
public static ClaimSet CreateIssuerClaimSet()
{
  return new DefaultClaimSet(Claim.CreateUriClaim(new
Uri(ClaimsAuthorizationPolicy.IssuerUri)),
Claim.CreateDnsClaim(ClaimsAuthorizationPolicy.IssuerName),
Claim.CreateNameClaim(ClaimsAuthorizationPolicy.IssuerName));
}

By making this change, the ClaimsPrincipalPermission is able to validate the issuer since only one of the claims must match.

Summary

In both parts of this article I have explained my approach to building a custom claims-based security model for your WCF services. Obviously to achieve this model it is necessary to establish a set of well-defined claims associated with service operations and application features. The idea being that those claims would should be required to access operations. For example, claims can be representative of features and actions users may take in the application, or claims can carry information about the caller that is useful to determine their rights. You should define a set of claims relevant to the application, so that operations can require claims to access certain features, and as new features are added to the application, new claims can be introduced.

Resources

Code for this article: http://www.dasblonde.net/downloads/tssclaimsbasedpart2.zip

Learning WCF, O’Reilly 2007, by Michele Leroux Bustamante (see my book blog for the code: http://www.thatindigogirl.com ). See my Security chapter for some background on WCF security.

Authors

Michele Leroux Bustamante is Chief Architect of IDesign Inc., Microsoft Regional Director for San Diego, Microsoft MVP for Connected Systems and a BEA Technical Director. At IDesign Michele provides training, mentoring and high-end architecture consulting services focusing on Web services, scalable and secure architecture design for .NET, interoperability and globalization architecture. She is a member of the International .NET Speakers Association (INETA), a frequent conference presenter, conference chair for SD West, and is frequently published in several major technology journals. Michele is also on the board of directors for IASA (International Association of Software Architects), and a Program Advisor to UCSD Extension. Her latest book is Learning WCF (O'Reilly 2007) - see her book blog here: www.thatindigogirl.com. Reach her at mlb@idesign.net, or visit www.idesign.net and her main blog at www.dasblonde.net.

News | Blogs | Discussions | Tech talks | White Papers | Downloads | Articles | Media kit | About
All Content Copyright ©2007 TheServerSide Privacy Policy
Site Map