Sponsored Links


Resources

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

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

March 28, 2007

Traditional security models for intranet and Internet applications use some form of username and password to authenticate users. Client-server applications deployed to a common domain often rely on Windows credentials (NTLM or Kerberos), while services exposed to the Internet often require a username and password to be passed in an interoperable format (WS-Security) to be authenticated against a custom credential store. These scenarios are frequently accompanied by role-based security checks that authorize access to functionality. Although popular, role-based security is often too coarse an approach since custom roles are often necessary to represent different combinations of permissions or rights. Thus, applications are usually better off authorizing calls based on permissions granted, using roles to gather the appropriate permission set. This type of permission-based security model will provide a more fine-grained result over role-based security – the downside is that .NET doesn’t inherently support it so it requires more work to implement.

WCF introduces a claims-based approach to security at service boundaries, improving on role-based and permission-based security models. Claims can represent many different types of information including identity, roles, permissions or rights and even general information about the caller that may be useful to the application. A set of claims is also vouched for by an issuer such as a security token service, adding credibility to the information described by each claim – something not present in role-based or permission-based models. An additional benefit of using a claims-based security model is that it supports federated and single sign-on scenarios.

This two-part article will explain how claims-based security is supported by WCF, and show you how to implement a claims-based security model for your services. In this first article I’ll start by providing a quick review of the traditional role-based model most .NET applications rely on, and then I’ll compare this model to the claims-based model supported by WCF. In the process, I’ll explain how different security tokens are converted into claims, how the security context for each request is initialized with those claims, and how you can interact with claims to authorize calls. I’ll also explain how to define custom claims for an application, how to normalize different credentials into that set of claims using a custom authorization policy, and how to handle authorization centrally or at each service operation. In the second article I’ll show you how to refine this claims-based authorization model working with custom security principals and claims-based permission demands. I’ll then explain how you can decouple authentication using security token services or CardSpace, and how to work with SAML tokens that carry normalized claims.

WCF and Role-Based Security

Any application can leverage built-in role-based security features of .NET including Windows client applications, ASP.NET web applications, ASP.NET web services built with ASMX and WCF services. In all cases, when users are authenticated a security principal is attached to the executing thread. This security principal holds the caller’s identity, which may be tied to a Windows account or a custom credential, and its roles. Security principals are the heart of role-based security – used to perform authorization checks in code. This section will review how this works and how WCF services work with this role-based security model.

Security Principals and Identities

To review, a security principal is a type that implements the IPrincipal interface which includes the following members:

public interface IPrincipal
{
      bool IsInRole(string role);
      IIdentity Identity { get; }
}

Implementations of this interface should return true from the IsInRole() method if the associated user belongs to the specified role. The user is identified by the Identity property which is a type that implements the IIdentity interface shown here:

public interface IIdentity
{
      string AuthenticationType { get; }
      bool IsAuthenticated { get; }
      string Name { get; }
}

Essentially the IIdentity implementation should hold the authenticated user’s name and the authentication type.

These interfaces enable standardized access to authenticated users and associated roles. When users are authenticated, an IIdentity type is created based on the type of credential provided. Built-in types for various .NET applications include WindowsIdentity, FormsIdentity, PassportIdentity, X509Identity and GenericIdentity. Based on the authorization policy for the application, the security principal can be a WindowsPrincipal, GenericPrincipal or RoleProviderPrincipal – the latter of which is new to WCF.

Security Principals and WCF

When a service operation is invoked, the request thread will have a security principal attached. The type of IIdentity associated with that principal is based on the credential type received at the service. The type of IPrincipal is based on the authorization mode for the service. Let’s walk through two common examples, for Windows and UserName credential types.

Consider this service configuration with a WSHttpBinding endpoint that requires a Windows credential passed via message security mode:

<system.serviceModel>
  <services>
    <service name="RoleBasedServices.SecureServiceInternal" >
      <endpoint contract="RoleBasedServices.ISecureService" 
binding="wsHttpBinding" bindingConfiguration="wsHttpWindows" />
    </service>
  </services>
  <bindings>
    <wsHttpBinding>
      <binding name="wsHttpWindows">
        <security mode="Message">
          <message clientCredentialType="Windows" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
</system.serviceModel>

When the Windows credential is passed by the client, it is serialized to a security token in the message. By default that token is authenticated and authorized against the Windows domain using NTLM or Kerberos (depending on your set up). For Windows credentials, the only authentication option is the Windows domain. By default, authorization relies on the Windows domain as well, per the PrincipalPermissionMode setting. The following illustrates a service behavior configuration showing the default settings for Windows authentication and authorization:

<behavior name="internalServiceBehavior">
  <serviceAuthorization 
principalPermissionMode="UseWindowsGroups" />
  <serviceCredentials>
    <windowsAuthentication includeWindowsGroups="true" 
allowAnonymousLogons="false" />
  </serviceCredentials>
</behavior>

With this configuration, the resulting security principal attached to the thread is a WindowsPrincipal with a WindowsIdentity reference.

In the case of UserName credentials, if the client passes a username and password that should be authenticated against a custom credential store, and not the Windows domain, you must explicitly configure the authentication and authorization modes. By default, UserName credentials are authenticated against the Windows domain, unless you provide an alternate UserNamePasswordValidationMode. Likewise, the default authorization mode uses the Windows domain unless you provide an alternate PrincipalPermissionMode. The following configuration illustrates a service that requires UserName credentials, and authenticates and authorizes against the configured ASP.NET membership and roles provider:

<system.serviceModel>
  <services>
    <service name="RoleBasedServices.SecureServiceExternal" 
behaviorConfiguration="externalServiceBehavior">
      <endpoint contract="RoleBasedServices.ISecureService" 
binding="wsHttpBinding" bindingConfiguration="wsHttpUsername" />
    </service>
  </services>
  <bindings>
    <wsHttpBinding>
      <binding name="wsHttpUsername">
        <security mode="Message">
          <message clientCredentialType="UserName" 
negotiateServiceCredential="false"
establishSecurityContext="false" />
        </security>
      </binding>
    </wsHttpBinding>
  </bindings>
  <behaviors>
    <serviceBehaviors>
      <behavior name="externalServiceBehavior">
        <serviceAuthorization principalPermissionMode="UseAspNetRoles" />
        <serviceCredentials>
          <userNameAuthentication 
userNamePasswordValidationMode="MembershipProvider" />
          <serviceCertificate findValue="RPKey" 
x509FindType="FindBySubjectName" storeLocation="LocalMachine" 
storeName="My"/>
        </serviceCredentials>
      </behavior>
    </serviceBehaviors>
  </behaviors>
</system.serviceModel>

To authenticate using the ASP.NET membership provider, you set the authentication mode to MembershipProvider instead of Windows (the default). To authorize using the ASP.NET roles provider, you set the authorization mode to UseAspNetRoles instead of UseWindowsGroups (the default). Unless you configure an alternate ASP.NET membership and roles provider, this means the default ASP.NET membership and roles provider – but you can create a custom provider to go against your own credential store if desired.

In this case a RoleProviderPrincipal is attached to the thread instead of a WindowsPrincipal. It references a GenericIdentity type carrying the username.

For both Windows and UserName credentials, the security principal attached to the thread supports role-based security since they respectively have access to the Windows or custom roles for the user. I’ll review how permission demands interact with the security principal shortly.

ServiceSecurityContext

In the case of WCF services, you can access information about the user through two means: the security principal attached to the thread, and the service security context. Put another way, the service model populates both the CurrentPrincipal property of the thread and the ServiceSecurityContext when it processes security tokens. For traditional role-based security, the CurrentPrincipal property is used to gain access to the security principal and the user identity:

IPrincipal principal = Thread.CurrentPrincipal;
IIdentity identity = Thread.CurrentPrincipal.Identity;

You can also access the user identity through the ServiceSecurityContext. This type also provides run-time access to other relevant information about the security context for a service operation - specifically, it provides access to identities, claims, and authorization policies as follows:

  • PrimaryIdentity: Contains a reference to the IIdentity type representing the authenticated caller.
  • WindowsIdentity: Contains a reference to the same IIdentity as the PrimaryIdentity property if it is a WindowsIdentity type.
  • AuthorizationContext: Contains information about the security token(s) passed in the message. This information is useful for performing claims-based authorization which I’ll discuss shortly.
  • AuthorizationPolicies: Contains information about the authorization policies used to grant claims, also to be discussed.

You can access the ServiceSecurityContext through the current OperationContext as shown here:

ServiceSecurityContext context = OperationContext.Current.ServiceSecurityContext;

Or, you can reach it directly through the static Current property:

ServiceSecurityContext context = ServiceSecurityContext.Current;

So, what’s the significance of the security principal attached to the thread and the security context? The security principal is used for role-based security consistent with pre-WCF role-based security techniques, while the ServiceSecurityContext is a WCF-specific abstraction that is most useful when you move away from role-based security to claims-based security as I will discuss.

Role-Based Permission Demands

.NET role-based security relies on the IPrincipal object attached to the thread to perform authorization checks. To authorize access to WCF operations you can use a PrincipalPermission demand to find out if the user is authenticated, if they are in a particular role, or if a particular user is calling (not as useful). Using an imperative permission demand within the WCF operation means creating a PrincipalPermission instance, initializing the values to enforce, and issuing a Demand():

public string AdminsOnly()
{
  // unprotected code

  PrincipalPermission p = new PrincipalPermission(null, "Administrators");
  p.Demand();
  
  // protected code
}

In this example, an exception will be thrown if the user is not in the Administrators group. You can also do this declaratively with the PrincipalPermissionAttribute:

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

Ultimately, both of these scenarios use the PrincipalPermission type to validate the thread’s security principal. This is done by checking the IsAuthenticated property of the IIdentity type and invoking the IsInRole() method of the IPrincipal type to check membership.

Internally, each IPrincipal type has its own way to enforce role checks when IsInRole() is called. In the case of the RoleProviderPrincipal it uses the ServiceSecurityContext to access the user’s identity, and it relies on the configured membership provider to check roles.

WCF and Claims-Based Security

Clearly, applying role-based permission demands is an easy approach to authorizing callers at the service tier. But, as I mentioned earlier, roles are very coarse and are prone to customization and change. Permissions are more granular, but don’t carry guarantees and aren’t first class citizens in any .NET security model. Claims, on the other hand, are a first class citizen with WCF.

Claims

Credentials are passed in the security headers of a message as security tokens. Each security token in a message is mapped to a set of claims that are added to the security context for the request. A claim describes an individual right or action applicable to a particular resource. For example, an identity claim might describe a username, while a proof of possession claim might describe a role, a right to a particular resource, or some other useful information.

Claims are represented at runtime as a Claim type, from the System.IdentityModel.Claims namespace, with the following key properties:

  • ClaimType: Can be any Uri value identifying the type of claim. A basic set of claims used by WCF can be found in the ClaimTypes static class, but this can be customized.
  • Resource: Can be any type, but should contain the resource being referred to by the claim. For an e-mail possession claim, this would contain an e-mail address as a string.
  • Right: Can be a Uri identifying an identity claim, or a possession claim. The Rights static class contains the correct Uri to use.

You can programmatically construct a Claim by providing a ClaimType, Resource and Right. The following code shows how to create a Claim that represents possession of an email address:

Claim c = new 
Claim("http://schemas.xmlsoap.org/ws/2005/05/identity/ 
claims/emailaddress", "mlb@idesign.net", 
http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty);

You can also rely on the ClaimTypes and Rights static classes instead of looking up the correct Uri for each:

Claim c = new Claim(ClaimTypes.Email, "mlb@idesign.net", 
Rights.PossessProperty);

The types of claims associated with the security context depend on the type of token, as I’ll discuss next.

Claim Sets

A claim set is a collection of claims granted by a particular issuer. For example, the claims extracted from an X.509 token are vouched for by the certificate authority. The certificate authority is therefore the issuer of the claim set. A claim set is represented at runtime by the ClaimSet type. Key properties for this type are:

  • Issuer: Another ClaimSet describing the issuer.
  • A collection of claims the issuer vouches for.

WindowsClaimSet, UserNameClaimSet and X509ClaimSet are examples of types that inherit the ClaimSet base type. These are the claim sets generated for Windows, UserName and X.509 tokens, respectively. To give you some context for what goes into a ClaimSet, let’s look at a few examples relevant to some common credentials used for authentication.

A WindowsClaimSet includes information about the identity of the user, plus other information about windows groups and other security identifiers. The following table shows a list of identity and possession claims for a Windows token:

Right:ClaimType

Resource

http://schemas.xmlsoap.org/ws/2005/05/identity/right/identity: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-5-21-2747712036-3657831467-4081887108-1000

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-5-21-2747712036-3657831467-4081887108-1000

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name

IDesign\\Administrator

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-5-21-2747712036-3657831467-4081887108-513

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-1-0

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-5-32-544

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-5-32-545

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-5-4

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-5-11

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-5-15

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-2-0

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/sid

S-1-5-64-10

You’ll notice a few things about these claims:

  • There is a single identity claim that is an SID associated with a Windows token identifier.
  • The remaining claims are possession of property claims that provide additional information about the user. Those include a name claim with the user name associated with the token; and other SID claims representative of other token information and groups associated with the user.

The issuer can also be described as a WindowsClaimSet, representative of the Windows account for the machine where the claims were issued.

A UserNameClaimSet produces a completely different set of claims as shown in this table:

Right:ClaimType

Resource

http://schemas.xmlsoap.org/ws/2005/05/identity/right/identity: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name

Admin

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name

Admin

This time you’ll notice that a single identity and possess property claim are provided – both indicating the user name – and that no roles are included in this claim set. The issuer of a UserNameClaimSet is usually a DefaultClaimSet indicating the system (or, application) issued the claims. This table lists the claims inside the issuer’s DefaultClaimSet:

Right:ClaimType

Resource

http://schemas.xmlsoap.org/ws/2005/05/identity/right/identity: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system

System

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/system

System

For X.509 tokens, the X509ClaimSet describes information related to the certificate as shown here:

Right:ClaimType

Resource

http://schemas.xmlsoap.org/ws/2005/05/identity/right/identity: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/thumbprint

cb d5 50 10 f1 65 af 3d 3b be a7 03 fb fe 87 28 89 06 e4 b6

://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/thumbprint

cb d5 50 10 f1 65 af 3d 3b be a7 03 fb fe 87 28 89 06 e4 b6

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/x500distinguishedname

CN=SubjectKey

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/dns

SubjectKey

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name

SubjectKey

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.xmlsoap.org/ws/2005/05/identity/claims/rsa

RSACryptoServiceProvider instance

The issuer for the X509ClaimSet depends on the type of certificate it represents. For example, if the certificate is self-issued, so is the claim set, which means the Issuer references itself. If the certificate is the root certificate, then the issuer should be the Certificate Authority certificate, otherwise it could represent another certificate in the chain.

The point of this discussion is to help you visualize the fact that every security token can be mapped to a set of claims, and that the service model extracts relevant claims for tokens it knows about. So far, I have focused only on Windows, UserName and Certificate credentials. What you should take away is the following:

  • For Windows credentials, clearly it is easier to work with role-based security rather than resolving SIDs to Windows groups using the claim set.
  • For UserName credentials, role-based security is the obvious choice since no roles are included in the claim set.
  • For Certificate credentials, since there is no role-based security (unless you map certificates to Windows accounts) you might find the claim set useful for doing custom authorization. For example you might have a database that associates certificates with custom roles or claims.

You can construct a custom claim set by either extending the DefaultClaimSet type or by constructing a DefaultClaimSet instance and providing a list of claims along with an issuer. The following example creates a claim set that describes an issuer using a name claim:

Claim c =Claim.CreateNameClaim( 
"http://www.thatindigogirl.com/samples/2006/06/issuer");
Claim[] claims = new Claim[1];
claims[0] = c;
ClaimSet issuer = new DefaultClaimSet(claims);

Since an issuer is not provided to the DefaultClaimSet constructor, the resulting ClaimSet is considered to be self-issued, and the Issuer property will reference itself. You can use this issuer claim set to construct a custom claim set for an application, as you’ll see later on.

AuthorizationContext

As I’ve discussed, when clients send credentials they are serialized as security tokens. The service model processes each security token and generates claims that vary based on the type of token. The claims are stored in the security context, accessible through the AuthorizationContext shown here:

AuthorizationContext authContext = 
ServiceSecurityContext.Current.AuthorizationContext;

The AuthorizationContext includes one or more claim sets based on the authenticated security tokens from the incoming request message, and associated authorization policies. You can programmatically access each claim set and drill down to extract specific claims for authorization. For example, inside a service operation you might look for an X509CertificateClaimSet and check the DNS claim for a particular subject key:

AuthorizationContext authContext = 
ServiceSecurityContext.Current.AuthorizationContext;

X509CertificateClaimSet certClaims = null;
foreach (ClaimSet c in authContext.ClaimSets)
{
  certClaims = c as X509CertificateClaimSet;
  if (certClaims != null)
    break;
}

if (certClaims == null)
  throw new SecurityException("Access is denied. X509CertificateClaimSet is required.");

if (!certClaims.ContainsClaim(Claim.CreateDnsClaim("RPKey")))
  throw new SecurityException("Access is denied.");

This example illustrates how you can access claims through the AuthorizationContext, based on the types of claim sets discussed so far. In reality it is more likely that you’ll map the incoming certificate to a custom set of roles or claims in order to standardize how you authorize callers to each operation. The AuthorizationContext becomes very useful in accessing custom claims attached to the security context that have specific meaning to your application. This is what I’ll focus on for the remainder of this article.

Building a Claims-Based Authorization Model

Now that you have some background on how claims are associated with the AuthorizationContext, and how you can access those claims to authorize calls - I’ll explain how to build a claims-based security model at the service tier using a custom authorization policy. The idea behind this is that your applications and services may need to support many credential types to support internal and external clients. Figure 1 illustrates this.

Figure 1: Services can be configured to support different credential types through security policy.

NOTE: All endpoints for a service share behavior, so you are more likely to have a common base service type with service logic, while exposing several derived types with unique endpoints and authorization behavior.

If services focus on authorization based on a normalized set of claims, the security policy can change without affecting service authorization, or downstream business rules based on user rights. Service developers should be able to work with a specific set of claims required to access features and interact with business rules – and ideally these claims are not coupled to a particular credential or set of roles. One way to achieve this is with a custom authorization policy as shown in Figure 2.

Figure 2: A custom authorization policy can produce a normalized set of claims for all credentials so that services can focus on a single set of claims for authorization.

In this case, the authorization policy is responsible for normalizing claims based on credentials provided, and the service authorizes against this consistent set of claims.

Defining Custom Claims

Before you can implement a claims-based model, you must consider what types of claims make sense to your application. If you think of claims as permissions that are required to access features, one thing you can do is create a list of these permissions, and the resources or features they relate to if applicable, and then formulate a set of custom claim types and resources based on this information.

Consider CRUD (Create, Read, Update, and Delete) operations related to resources in an application. If the application has two key features related to Customers and Orders, you might want to specify CRUD rights for each resource that can later be allocated to users as they are authenticated. The following table summarizes the possible claims:

Right:ClaimType

Resource

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.thatindigogirl.com/samples/2006/06/identity/claims/create

http://schemas.thatindigogirl.com/samples/2006/06/identity/resources/customers

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.thatindigogirl.com/samples/2006/06/identity/claims/read

http://schemas.thatindigogirl.com/samples/2006/06/identity/resources/customers

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.thatindigogirl.com/samples/2006/06/identity/claims/update

http://schemas.thatindigogirl.com/samples/2006/06/identity/resources/customers

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.thatindigogirl.com/samples/2006/06/identity/claims/delete

http://schemas.thatindigogirl.com/samples/2006/06/identity/resources/customers

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.thatindigogirl.com/samples/2006/06/identity/claims/create

http://schemas.thatindigogirl.com/samples/2006/06/identity/resources/orders

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.thatindigogirl.com/samples/2006/06/identity/claims/read

http://schemas.thatindigogirl.com/samples/2006/06/identity/resources/orders

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.thatindigogirl.com/samples/2006/06/identity/claims/update

http://schemas.thatindigogirl.com/samples/2006/06/identity/resources/orders

http://schemas.xmlsoap.org/ws/2005/05/identity/right/possessproperty: http://schemas.thatindigogirl.com/samples/2006/06/identity/claims/delete

http://schemas.thatindigogirl.com/samples/2006/06/identity/resources/orders

To work with custom claims and resources in code you can create a list of representative constants as follows:

public static class ClaimTypes
{
  public const string Create = "http://schemas.thatindigogirl.com/
samples/2006/06/identity/claim/create";
  public const string Read = "http://schemas.thatindigogirl.com/
samples/2006/06/identity/claim/read";
  public const string Update = "http://schemas.thatindigogirl.com/
samples/2006/06/identity/claim/update";
  public const string Delete = "http://schemas.thatindigogirl.com/
samples/2006/06/identity/claim/delete";
}

public static class Resources
{
  public const string Customers = "http://schemas.thatindigogirl.com/
samples/2006/06/identity/resources/customers";
  public const string Orders = "http://schemas.thatindigogirl.com/
samples/2006/06/identity/resources/orders";
}

Services would then be designed to perform authentication against these claims, as appropriate for the operation being invoked. For example, an operation that will delete a customer should check that one of the claims provided in the authorization context includes the ClaimTypes.Delete right for Resources.Customers. As new features are added to the application, new claims may also be introduced to the authorization model, but if designed properly existing operations wouldn’t need to change their authorization requirements.

It may also be important who issued the claim. For example, you may want to look for a claim set by a trusted issuer before checking for particular claims within the claim set. When you’re using a custom authorization policy, as I will discuss in the coming sections, the issuer might just be the application as it was for the UserNameClaimSet discussed earlier, or a named internal issuer. In the article to follow this one, I will talk about trusting external issuers.

Implementing IAuthorizationPolicy

Once you have established your requirements for claims-based authorization, you can implement a custom authorization policy to handle mapping different credentials and tokens to your normalized application claims. A custom authorization policy is a type that implements the IAuthorizationPolicy interface from the System.IdentityModel.Policy namespace. Authorization policies can inspect the claims that will be attached to the security context, before operations are called, and attach new claim sets to that context. In addition, a custom authorization policy is required to provide a security principal to be attached to the request thread – in lieu of it being done automatically through default authentication.

To implement a custom authorization policy, you create a type that implements IAuthorizationPolicy. This interface also inherits IAuthorizationComponent, which means you must also provide implementation for its members. Definitions for the two interfaces are shown here:

public interface IAuthorizationPolicy : IAuthorizationComponent
{
  ClaimSet Issuer { get; }

  bool Evaluate(EvaluationContext evaluationContext, 
ref object state);
}

public interface IAuthorizationComponent
{
  string Id { get; }
}

The functionality for each member should be implemented as follows:

  • Id returns a unique identifier for the authorization policy instance. This can be a unique GUID.
  • Issuer returns a ClaimSet describing the issuer associated with the authorization policy. If claims are generated by this authorization policy, it must provide an issuer when generating the claim set for the authorization context.
  • The Evaluate() method receives the claim sets evaluated so far by other authorization policies. For example, it may include a claim set for each token passed in the request message, thus contain a WindowsClaimSet or UserNameClaimSet and so on.

Evaluate() is the heart and soul of this implementation – responsible for inspecting claims based on the credentials provided, mapping those claims to normalized claims, and constructing a security principal for the request thread. The method should return false if this authorization policy was not able to complete its authorization. If false, the service model will invoke other authorization policies and then call this one once more, passing the updated claim sets. This gives the authorization policy another chance to authorize calls.

The EvaluationContext passed to Evaluate() provides access to the user identity through its Properties dictionary. If you are looking for a particular type of identity, such as a WindowsIdentity or GenericIdentity you can check for this before mapping the identity to custom claims. The following code expects a single identity of type GenericIdentity, for services that require UserName credentials:

  object objIdentities = evaluationContext.Properties["Identities"];
  IList<IIdentity> identities = objIdentities as IList<IIdentity>;
  if (identities != null && identities.Count > 0)
  {
    IIdentity identity = identities[0] as GenericIdentity;
    if (identity == null)
      // can�t authorize
    else
      // can authorize
  }

If you write an authorization policy that specifically handles normalizing claims for UserName credentials, this is acceptable. You can optionally change this to support different identity types.

Once you have the identity, you can decide how to map that identity to normalized claims, building a ClaimSet for the authorization context. One approach could be to map roles to claims, which means looking up the roles for the identity to achieve this. The following MapClaims() function accepts an identity, expecting to use the ASP.NET provider model to find its roles and map those roles to claims:

protected virtual ClaimSet MapClaims(IIdentity identity)
{
  List<Claim> listClaims = new List<Claim>();

  if (Roles.IsUserInRole(identity.Name, "Guests"))
  {
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Read, 
ClaimsAuthorizationPolicy.Resources.Customers,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Read, 
ClaimsAuthorizationPolicy.Resources.Orders,
Rights.PossessProperty));
  }
  else if (Roles.IsUserInRole(identity.Name, "Users"))
  {
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Read, 
ClaimsAuthorizationPolicy.Resources.Customers,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Read, 
ClaimsAuthorizationPolicy.Resources.Orders,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Create, 
ClaimsAuthorizationPolicy.Resources.Customers,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Create, 
ClaimsAuthorizationPolicy.Resources.Orders,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Update, 
ClaimsAuthorizationPolicy.Resources.Customers,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Update, 
ClaimsAuthorizationPolicy.Resources.Orders,
Rights.PossessProperty));

  }
  else if (Roles.IsUserInRole(identity.Name, "Administrators"))
  {
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Read, 
ClaimsAuthorizationPolicy.Resources.Customers,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Read, 
ClaimsAuthorizationPolicy.Resources.Orders,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Create, 
ClaimsAuthorizationPolicy.Resources.Customers,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Create, 
ClaimsAuthorizationPolicy.Resources.Orders,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Update, 
ClaimsAuthorizationPolicy.Resources.Customers,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Update, 
ClaimsAuthorizationPolicy.Resources.Orders,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes.Delete, 
ClaimsAuthorizationPolicy.Resources.Customers,
Rights.PossessProperty));
    listClaims.Add(new Claim(ClaimsAuthorizationPolicy.ClaimTypes. Delete, 
ClaimsAuthorizationPolicy.Resources.Orders,
Rights.PossessProperty));
  }


  return new DefaultClaimSet(ClaimSet.System, listClaims);
}

For each role associated with UserName credentials, the authorization policy has established a list of claims that are appropriate. At the end of the function a new ClaimSet is created, in this case a DefaultClaimSet. You must provide the list of claims and an issuer for the claim set. In this case, the issuer is the application, identified by ClaimSet.System. You can also construct a custom claim set to identify the issuer – to distinguish claims. For example, you can construct a name claim with a custom Uri as shown here:

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

Claim c = Claim.CreateNameClaim(ClaimsAuthorizationPolicy.IssuerName);
Claim[] claims = new Claim[1];
claims[0] = c;
DefaultClaimSet issuerClaimSet = new DefaultClaimSet(claims);

This approach allows you to check for the application trusted claim set when authorizing requests.

Once the normalized claim set is generated, you must attach it to the EvaluationContext so that the service model can add it to the AuthorizationContext. In addition, you must add a security principal to the EvaluationContext so that the service model can attach it to the request thread, and to the security context as discussed earlier. The complete implementation of the Evaluate() function for this custom authorization policy, addressing these requirements, is 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;
}

MapClaims() returns the claim set to attach to the evaluation context and a new GenericPrincipal without any roles is constructed to provide a security principal for the request thread. In this case, role-based security will be useless since no roles are assigned – but the AuthorizationContext will support claims-based security.

NOTE: You do not have to look at roles to map claims. Instead, you could look at the claim sets attached to the evaluation context, and determine how to assign normalized claims from the generated claims.

Configuring Custom Authorization Policies

To configure a custom authorization policy you set the PrincipalPermissionMode to Custom for the service authorization behavior, and add the IAuthorizationPolicy type to the authorization policies section as follows:

<serviceAuthorization principalPermissionMode="Custom" >
  <authorizationPolicies>
    <add policyType=
"ClaimsBasedSecurityComponents.ClaimsAuthorizationPolicy, ClaimsBasedSecurityComponents"/>
  </authorizationPolicies>
</serviceAuthorization>

NOTE: this can also be done programmatically through the ServiceHost instance.

By stating you are performing custom authorization, you are expected to supply the security principal for the thread, as I have discussed. You can configure one or more authorization policy, in the order you want them executed. This can be useful if you support different types of credentials at the service, and want to provide different authorization policies to handle each credential type for better reuse.

Once configured, the authorization policy will be instantiated for each request to the service, and given a change to supply information for the security context.

Working with Normalized Claims

Earlier in the article I said that the goal was to allow developers to standardize on a set of claims to authorize calls to each operation. As discussed earlier, the traditional role-based security model meant applying authorization checks at the operation level using permission demands either in code or using the PrincipalPermissionAttribute. Once your custom authorization policy is in place, you can replace these permission demands with claims-based authorization checks against the AuthorizationContext.

Consider a DeleteCustomer() operation that requires callers are granted delete rights to the customers resource. Inside the operation you can check the AuthorizationContext for a claim issued by your custom application issuer that matches this requirement:

public string DeleteCustomer()
{
  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.");

This code traverses the AuthorizationContext for a claim set that matches the issuer identified by the name claim mentioned earlier: "http://www.thatindigogirl.com/samples/2006/06/issuer". If a claim set from the expected issuer is present, the code proceeds to look for a particular claim. In this case, the delete claim for customers.

Of course, you probably wouldn’t want to litter service operation code with this cumbersome block of code to authorize callers. That’s where we get into other techniques for authorizing these normalized claims, to be discussed in the next part of this article.

Summary

In this article you learned how role-based security and claims-based security differ, and how to implement a claims-based security model. The goal of this first part was to educate you on claims and claim sets, help you understand how tokens map to claims, and educate you on the process of creating your own custom claims model. In addition, you learned how to create a custom authorization policy that can normalize claims for all tokens, and insert those new claims into the authorization context for evaluation in lieu of role-based security checks.

In the next part of this article, I’ll discuss several techniques for refining the authorization of custom claims. Instead of performing authorization directly in the operation I’ll show you how to extend the role-based security model of .NET for a claims-based model, I’ll also compare that approach to a custom validation policy or custom security token manager implementation. In addition, I’ll look at how the model works with SAML tokens issued by a custom security token service (STS).

Resources

Code for this article: http://www.dasblonde.net/downloads/tssclaimsbasedpart1.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