Security Briefs

Exploring Claims-Based Identity

Keith Brown

Code download available at: Security Briefs2007_09.exe(206 KB)

Contents

Finding Common Ground
Traditional Representation of Client Identity
ClaimSets and Claims
Claims Transformation and IAuthorizationPolicy
Security Token Service
Trust and Federated Identity
Until Next Time

Most enterprise applications need some basic user security features. At a minimum, they need to authenticate their users, and many also need to authorize access to certain features so that only privileged users can get to them. Some apps must go further and audit what the user does. On Windows®, these features are built into the operating system and are usually quite easy to integrate into an application. By taking advantage of Windows integrated authentication, you don't have to invent your own authentication protocol or manage a user database. By using access control lists (ACLs), impersonation, and features such as groups, you can implement authorization with very little code. Indeed, this advice applies no matter which OS you are using. It's almost always a better idea to integrate closely with the security features in your OS rather than reinventing those features yourself.

But what happens when you want to extend reach to users who don't happen to have Windows accounts? What about users who aren't running Windows at all? More and more applications need this type of reach, which seems to fly in the face of traditional advice. This column will introduce you to the new identity model in the Microsoft® .NET Framework 3.0, which is designed to help address these and other problems.

Finding Common Ground

On Windows, the most common type of credential used to access an enterprise application is simply the user's domain account. An application that uses Windows integrated authentication receives a Kerberos ticket to represent a client. An application using SSL might instead receive an X.509 certificate for the client. A Kerberos ticket and an X.509 certificate are two very different beasts, and the code in the OS that parses, validates, and ultimately presents the data inside is very different. But if you take a step back and think about what they really represent, you'll see that these two credentials have a lot in common.

To make this discussion more concrete, and to help introduce what might be new terminology for some readers, let's imagine that Alice is a user who wants to access a purchasing service using her Windows domain account. Her domain controller authenticates her and creates a Kerberos ticket with a bunch of security identifiers (SIDs) in it. These SIDs represent Alice's user account as well as the domain groups in which she's a member, and they are embedded in the ticket along with a signature from the domain controller. In identity-speak, an issuer (the domain controller) has given a subject (Alice) a security token that she can use in order to prove her identity.

These same ideas apply when Alice uses a certificate instead. A certificate is just another type of security token. The issuer in this case is a certificate authority (CA) and the subject is Alice. Both the Kerberos ticket and the certificate are, in essence, signed statements by an issuer about a subject. These are just two different ways that a trusted authority can vouch for one of its subjects. Each signed statement can be thought of as a collection of claims. You should think of it this way: the domain controller is making claims about Alice's identity when it signs the list of SIDs in her ticket. Each SID becomes a claim. The certificate authority makes claims about Alice's identity when it signs her name and public key. The name and public key are examples of claims in the certificate.

If the terminology I'm using sounds a little academic, bear with me because these abstractions turn out to be incredibly useful in practice. In fact, the entire goal of this new identity model is to abstract identity in a way that reduces your dependency on specific types of credentials without compromising the security of your application. By coding to the identity model in the .NET Framework 3.0, you'll be able to process not only Kerberos tickets and certificates, but also Security Assertion Markup Language (SAML) tokens, which opens the door to some really interesting identity architectures, including federated identity.

Traditional Representation of Client Identity

That's enough abstract talk; let's see this stuff in action. I'll start with a simple Windows Communication Foundation (WCF) service that accepts either Windows credentials or X.509 certificates from clients. It exposes a single method that takes no arguments, and when called by a client, it prints out details about the client's identity. Figure 1 shows this method, called Hello. In this version, it uses the traditional IIdentity interface introduced in the .NET Framework 1.0 to inspect the client's identity.

Figure 1 Hello Method

public void Hello() {
    ServiceSecurityContext sctx = ServiceSecurityContext.Current;
    if (null == sctx) {
        Console.WriteLine("Security context is null."+
                          "User must not be authenticated.");
        return;
    }
    IIdentity id = sctx.PrimaryIdentity;
    Console.WriteLine("Primary identity type: {0}", id.GetType().Name);
    Console.WriteLine("AuthenticationType: {0}", id.AuthenticationType);
    Console.WriteLine("IsAuthenticated: {0}", id.IsAuthenticated);
    Console.WriteLine("Name: {0}", id.Name);
}

The first step here is to grab the ServiceSecurityContext for the call, which is how WCF communicates details of the client's identity to a service. I'm careful to check for null, which would indicate an anonymous client. Then I grab the PrimaryIdentity property and dump out its contents. Here's what the output looks like when I configure the sample to use Windows credentials:

Primary identity type: WindowsIdentity
AuthenticationType: NTLM
IsAuthenticated: True
Name: GROMIT\Alice

The concrete type in this case is WindowsIdentity, which exposes a lot more information than IIdentity. By downcasting to WindowsIdentity, I'd be able to get a list of the groups of which Alice is a member, get her user SID and use that to look up her user record in Active Directory®, and I might even be able to impersonate her in some circumstances.

Now let's run this same example with a different WCF configuration. This time I'll have the client submit an X.509 certificate.

Primary identity type: X509Identity
AuthenticationType: X509
IsAuthenticated: True
Name: CN=SampleClient; 33BB8518E4B7...

The Name property in this case is a combination of the common name in the client certificate and its thumbprint. If you wanted further information, such as the issuer, expiration date, and so on, you might try downcasting to X509Identity, but you'd quickly find that this class is marked internal. But regardless of this limitation, even if you could downcast, you'd be dealing with an entirely different programming model from that in WindowsIdentity. This is one problem that the new identity model solves: it gives you a single programming model no matter what shape of client credential you get. In the next section, I've rewritten the Hello sample to be claims-aware. Read on and see what identity looks like through a claims-based lens.

ClaimSets and Claims

In Figure 2, you'll see a new version of Hello that's been rewritten using classes in System.IdentityModel, the new identity model introduced by the .NET Framework 3.0. This model steps back and treats identity using the abstractions of subject, issuer, and claim. Issuers make claims about their subjects and sign those claims to create security tokens. Each statement from any given issuer is modeled as a ClaimSet, which is a collection of Claim objects coupled with a property called Issuer, which is simply another ClaimSet.

Figure 2 Hello Using System.IdentityModel

public void Hello() {
    ServiceSecurityContext sctx = ServiceSecurityContext.Current;
    if (null == sctx) {
        Console.WriteLine("Security context is null." +
                          "User must not be authenticated.");
        return;
    }
    AuthorizationContext actx = sctx.AuthorizationContext;

    Console.WriteLine();
    Console.WriteLine("HelloService.Hello sees {0} claim set(s):", 
        actx.ClaimSets.Count);
    Console.WriteLine();

    foreach (ClaimSet cs in actx.ClaimSets) {
        Console.WriteLine("------------------------------");
        displayClaimSet(cs);
    }
}

const string CLAIM_FORMAT_STRING = "{0, -5} {1, -15} {2}";

private void displayClaimSet(ClaimSet cs) {
    Console.WriteLine();
    Console.WriteLine(CLAIM_FORMAT_STRING, 
        "RIGHT", "CLAIM_TYPE", "RESOURCE");

    // display claims for subject of this claimset
    foreach (Claim c in cs) {
        displayClaim(c);
    }
    
    Console.WriteLine();
    Console.Write("ISSUED BY: ");

    // display claims for issuer of this claimset
    if (null == cs.Issuer) {
        Console.WriteLine("null");
    }
    else if (object.ReferenceEquals(cs, cs.Issuer)) {
        // self-asserted claims (issuer is the same as subject)
        Console.WriteLine("self");
    }
    else {
        displayClaimSet(cs.Issuer);
    }
}

// print a compact display of the supplied claim
private void displayClaim(Claim c) {
    string right;
    if (c.Right.Equals(Rights.Identity)) {
        right = "ID";
    }
    else if (c.Right.Equals(Rights.PossessProperty)) {
        right = "PP";
    }
    else right = c.Right;

    // make things a little more readable
    string claimType = c.ClaimType.Replace(
        "https://schemas.xmlsoap.org/ws/2005/05/identity/claims/", 
        ".../");
    string value = stringizeResource(c.Resource);

    Console.WriteLine(CLAIM_FORMAT_STRING, right, claimType, value);
}

private string stringizeResource(object resource) {
    // sometimes the claim's value will simply be a string
    string stringResource = resource as string;
    if (null != stringResource) return stringResource + " (string)";

    SecurityIdentifier sid = resource as SecurityIdentifier;
    if (null != sid) return getAccountName(sid) + " (SecurityIdentifier)";

    X500DistinguishedName dn = resource as X500DistinguishedName;
    if (null != dn) return dn.Name + " (X500DistinguishedName)";

    RSACryptoServiceProvider key = resource as RSACryptoServiceProvider;
    if (null != key) return string.Format(
        "{0} bit RSA public key (RSACryptoServiceProvider)", 
        key.KeySize);

    // for anything else, just display the type name
    return string.Format("({0})", resource.GetType().Name);
}

private string getAccountName(SecurityIdentifier sid) {
    try {
        return sid.Translate(typeof(NTAccount)).ToString();
    }
    catch (IdentityNotMappedException) {
        return sid.Value;
    }
}

Before drilling straight into the code, I want you to see some output so you can get a feeling for what a ClaimSet represents. Have a look at Figure 3, which shows the output of the new Hello method when the client uses Windows credentials. The displayClaimSet helper method in Figure 2 pretty-prints the list of claims in the ClaimSet, and then recursively walks up the chain of issuers, printing their ClaimSets in turn. Not surprisingly, with Windows credentials, the ClaimSet mainly consists of SIDs. Looking a bit more closely at this output, we can see how System.IdentityModel represents claims.

Figure 3 Output of the New Hello Method

HelloService.Hello sees 1 claim set(s):

------------------------------

RIGHT CLAIM_TYPE RESOURCE
ID    .../sid    GROMIT\Alice (SecurityIdentifier)
PP    .../sid    GROMIT\Alice (SecurityIdentifier)
PP    .../name   GROMIT\Alice (string)
PP    .../sid    GROMIT\None (SecurityIdentifier)
PP    .../sid    Everyone (SecurityIdentifier)
PP    .../sid    BUILTIN\Users (SecurityIdentifier)
PP    .../sid    NT AUTHORITY\INTERACTIVE (SecurityIdentifier)
PP    .../sid    NT AUTHORITY\Authenticated Users (SecurityIdentifier)
PP    .../sid    NT AUTHORITY\This Organization (SecurityIdentifier)
PP    .../sid    LOCAL (SecurityIdentifier)
PP    .../sid    NT AUTHORITY\NTLM Authentication (SecurityIdentifier)

ISSUED BY:
RIGHT CLAIM_TYPE RESOURCE
ID    .../sid    S-1-5 (SecurityIdentifier)
PP    .../sid    S-1-5 (SecurityIdentifier)

ISSUED BY: self

The Claim class has three key properties: ClaimType, Right, and Resource. In Figure 3, you can see two types of claims: a SID and a Name. The ClaimTypes for these are URIs, but I've abbreviated them in the displayClaim helper method by removing the first part of the URI, otherwise the output would be pretty tough to read.

While there is no centralized organization that defines all possible types of claims, we really don't need that. All we need is for the issuer to use claim types understood by the consumer (in this case, our Hello method). In practice, you'll see different claim types defined for use in different contexts. As an example, if you read the Information Card Profile, you'll see a dozen or so claim types defined for use with information cards.

Besides ClaimType, the Claim class also exposes a Right property that can often help you find a claim you need. Look once again at Figure 3 and note that of all the SIDs shown, only the first SID represents the subject's identity (Alice). The rest of them represent groups. The RIGHT column in the output shows the value of Claim.Right, which is technically a URI that is also abbreviated in the displayClaim routine. System.IdentityModel only defines two rights today: Identity and PossessProperty. You can see that WCF thinks that the user's SID in this example should be used as an identity claim. Of all the claims presented, it's the one you'd want to use to identify the user. All the other claims are PossessProperty claims.

Claim.Resource is the value of the claim. My claim dump code in Figure 2 has a helper method, stringizeResource, which pretty-prints these values along with their type names. You see, the Resource property is of type object, and can contain just about anything, depending on the type of the claim. For example, the SID claims you see in Figure 3 are represented by instances of the SecurityIdentifier class introduced in the .NET Framework 2.0.

In a nutshell, the Claim class tells you the type and value of the claim, and often gives you a hint as to whether the claim can be used to uniquely identify a subject or issuer. The ClaimSet is a collection of these Claim objects, coupled with a reference to the issuer's ClaimSet. In Figure 3, note that the issuer is represented with a SID of S-1-5. That SID represents the "NT Authority," which basically means the OS. Alice is using a local account in this example; otherwise the issuer would be identified by the SID of Alice's domain.

As you traverse up the chain of issuers, you'll eventually find one that is self-referential, that is, it points to its own ClaimSet. The displayClaimSet method in Figure 2 shows how you can check for this using Object.ReferenceEquals. This happens when you reach a root authority such as a domain controller or certificate authority. It's important to watch for this. Otherwise, if you are traversing up the issuer chain, you might just end up in an infinite loop when you reach the root issuer!

Now have a look at Figure 4, which shows the output of Hello when Alice sends a certificate. In this case, WCF uses Claim.Right to hint that the certificate's thumbprint can be used to identify the client. And since I'm cheating by using self-signed certificates for my sample code, the Issuer property points to the subject's claim set, indicating that it was self-issued. Note that different classes are used to represent the claims, but the overall structure is very similar to the previous example.

Figure 4 Hello Output when Alice Sends a Certificate

HelloService.Hello sees 1 claim set(s):

------------------------------

RIGHT CLAIM_TYPE      RESOURCE
ID    .../thumbprint  (Byte[])
PP    .../thumbprint  (Byte[])
PP    .../x500distinguishedname CN=SampleClient (X500DistinguishedName)
PP    .../dns         SampleClient (string)
PP    .../name        SampleClient (string)
PP    .../rsa         2048 bit RSA public key (RSACryptoServiceProvider)

ISSUED BY: self

Finally, if you look at Figure 5, you'll see the output of Hello when the client uses an information card to authenticate with the service. In this case, the client's e-mail address and first name were sent as claims, and because a personal card was used, the issuer is identified with an RSA public key. If you want to know more about information cards and what these particular ClaimSets mean, read my two recent columns on Windows CardSpace™ in the October 2006 and May 2006 issues of MSDN® Magazine (msdn.microsoft.com/msdnmag/issues/06/10/SecurityBriefs and msdn.microsoft.com/msdnmag/issues/06/05/SecurityBriefs).

Figure 5 Hello Output from Information Card

HelloService.Hello sees 2 claim sets:

------------------------------

RIGHT CLAIM_TYPE      RESOURCE
PP    .../hash        (Byte[])

ISSUED BY: self
------------------------------

RIGHT CLAIM_TYPE       RESOURCE
PP    .../emailaddress alice@contoso.com (string)
PP    .../givenname    Alice (string)

ISSUED BY:
RIGHT CLAIM_TYPE      RESOURCE
ID    .../rsa         2048 bit RSA public key (RSACryptoServiceProvider)
PP    .../rsa         2048 bit RSA public key (RSACryptoServiceProvider)

ISSUED BY:
RIGHT CLAIM_TYPE      RESOURCE

ISSUED BY: self

There are several advantages to building systems that use a claims-based lens to look at client identity. One obvious benefit is that you get a single programming model to inspect any kind of client identity. Just program against ClaimSet and you're all done, right? Well, in practice, if you're expecting a certificate, you're going to be looking for different types of claims than if you were expecting Windows credentials or a personal information card. In fact, there are classes that derive from ClaimSet, such as X509CertificateClaimSet and WindowsClaimSet, that can make things a little easier when you know what type of credential you are expecting. Having a single programming model is a good thing. But you'll discover the real power of claims once you realize that you can model just about any sort of authentication and authorization system using these concepts of subjects, issuers, and claims. And there's no better way to show this than through claims transformation.

Claims Transformation and IAuthorizationPolicy

Imagine that you need to build a WCF service that accepts lots of different types of credentials, and you want to use roles to authorize access to features. If the user is in the Manager role, she will have access to more features than if she is only in the Clerk role, for example. You can model roles using claims, and you can use claims transformation to deal with the multitude of different credential formats you might accept.

System.IdentityModel has an interface that allows you to perform arbitrary claims transformation. It's called IAuthorizationPolicy, and its one key method is called Evaluate, which takes an instance of the following class as an argument:

public abstract class EvaluationContext {
    // Methods
    protected EvaluationContext();
    public abstract void AddClaimSet(
        IAuthorizationPolicy policy, ClaimSet claimSet);
    public abstract void RecordExpirationTime(DateTime expirationTime);

    // Properties
    public abstract ReadOnlyCollection<ClaimSet> ClaimSets { get; }
    public abstract int Generation { get; }
    public abstract IDictionary<string, object> Properties { get; }
}

This context allows you to do a number of things, but what I care about here is that it allows you to examine the claim sets presented by a client, do some sort of mapping and then add your own claim set. Say, for example, the client submitted a certificate. Your authorization policy could map the subject name in the certificate onto a set of roles, and expose those roles as claims in a new ClaimSet that indicates your application as the issuer. If instead the user supplied a Windows credential, you could map her groups onto those same roles using a similar ClaimSet. Once this transformation occurs, your service simply needs to look for the ClaimSet issued locally, and then read the user's roles from there.

WCF has excellent support for this model. In your service's configuration file, you can wire up a ServiceAuthorization behavior that tells WCF to call your implementation of IAuthorizationPolicy before it calls your service's method (Hello, for example). This allows you to centralize the code that deals with the myriad of client credentials that your service accepts. Note that if you're planning to use role-based security, you can take this one step further and set the PrincipalPermissionMode attribute on the ServiceAuthorization behavior to Custom, and then supply an instance of IPrincipal that contains your roles via the evaluation context. This will allow you to use the PrincipalPermission attribute on your service's methods to grant access based on roles. Here's what this behavior looks like in config:

<serviceAuthorization principalPermissionMode="Custom">
  <authorizationPolicies>
    <add policyType="MyAuthorizationPolicy, MyAssembly"/>
  </authorizationPolicies>
</serviceAuthorization>

And here's how you communicate a custom principal to WCF using the evaluation context:

string[] roles = lookupRolesForUser(evaluationContext);
GenericIdentity id = new GenericIdentity(clientName);
evaluationContext.Properties["Principal"] =
    new GenericPrincipal(id, roles);

WCF will then set up Thread.CurrentPrincipal with the supplied principal before calling into the service's method. I only mention this custom principal trick because a lot of people like using PrincipalPermission and they will find this to be handy. But let's get back to claims again, because that's really where the power lies. So, how would you go about creating your own ClaimSet to represent a set of roles? While a full example can be found in the sample code available with this column on the MSDN Magazine Web site, here's an excerpt that shows how to do this:

string clientName = getClientNameFromClaims(EvaluationContext);
Claim c1 = new Claim(ClaimTypes.Name, clientName, Rights.Identity);
Claim c2 = new Claim("urn:role", "foo", Rights.PossessProperty);
Claim c2 = new Claim("urn:role", "bar", Rights.PossessProperty);
ClaimSet newClaimSet = new DefaultClaimSet(
    DefaultClaimSet.System, c1, c2, c3);

I used a well-known claim set called System to indicate that this is an internal claim set issued by my own code. The System claim set has nothing to do with the SYSTEM account in Windows. It's just a simple claim set that is easy to search for. Note that I add roles as PossessProperty claims, with a custom URI. You'll probably want to come up with something more elaborate than "urn:role" for the claim type URI, but since System.IdentityModel doesn't define a URI for a role claim, you can pick one that makes sense for your app.

Once you've created the new claim set, you can add it to the evaluation context via the AddClaimSet method. Now the code in your application simply needs to look for this ClaimSet you've issued, and it will get a list of roles for the user, regardless of whether she used a certificate or a Windows account to authenticate.

Security Token Service

Imagine for a moment that you've taken the time to build a great implementation of IAuthorizationPolicy that maps different credential types onto a simple set of role-based claims as I described above. You may have lots of different services that need this feature. One approach to reuse this code would be to drop the class into its own assembly and wire it into every service that needs it.

A more interesting approach would be to issue your own security tokens by using your authorization policy as the basis for implementing a Security Token Service (STS), which issues security tokens via a Web service protocol known as WS-Trust. An STS can accept a security token of one type and issue a token of another type, and can deal with whatever claims transforms are necessary, such as mapping a client certificate onto a SAML token that contains a set of roles that an application understands. In this scenario, the applications in your enterprise would look for a ClaimSet issued (and signed) by your own STS.

As I am writing this in June 2007, implementing an STS is possible, but not very easy. (Microsoft is working on making this easier.) Regardless of whether or not you happen to implement your own STS, building a claims-aware application will make it easier for you to leverage one should it ever become available to you.

Trust and Federated Identity

WCF and other communication frameworks use cryptography to ensure that the sender of a security token is indeed the subject and that the claims in the token were signed by the issuer named in the token. But all of this fancy plumbing doesn't have any idea how much you trust the issuer. If you don't trust him, you're not going to trust the claims he makes about his subjects! That's why the issuer is always identified when you receive a claim set, and it's the first thing you should look at when processing a claim set.

It's easy to write code that accepts tokens from a single trusted issuer. Just make sure the claim set you received was issued by the one authority you trust, and then you can use those claims to make security decisions. You've essentially delegated responsibility to the STS for doing the heavy lifting such as mapping users onto roles and dealing with different types of security tokens.

Now imagine you wanted to take this one step further. Instead of only accepting Windows credentials and X.509 certificates, what if your STS also accepted signed SAML tokens issued by an STS at a trusted partner? This leads to the realm of federated identity, which is very powerful. Instead of having to worry about managing user accounts for external users from partner companies, you can instead accept signed statements from those partners in the form of SAML tokens. I discuss the many benefits of automating these sorts of business-to-business trust relationships in my article on Active Directory Federation Services (msdn.microsoft.com/msdnmag/issues/06/11/SingleSignOn).

Federated identity ultimately boils down to claims transformation, if you think about it. The partner's STS makes the client's life easy by accepting as input whatever credential is most natural for her, given her operating system and platform. For example, if the client is running Windows, the STS could use Kerberos to automatically authenticate her and issue a SAML token. Another partner company might run a completely different OS that uses other strong authentication protocols. But the STS at that company would use those protocols to seamlessly authenticate the user and issue a SAML token. Meanwhile, the user enjoys the benefits of single-sign on, even when using applications like yours from federated partner companies.

The STS in your organization makes life easy for your applications. Your STS will ensure that only SAML tokens from trusted partners are accepted, and will then issue a new token for use with your application. Your application can live a long and happy life simply looking for ClaimSets issued by your company's STS. Figure 6 shows the flow of messages in this federated system. The client first goes to her company's account STS to obtain a SAML token. She sends this token to the resource STS to obtain a new SAML token that the service will consume.

Figure 6 Federated Authentication Message Flow

Figure 6** Federated Authentication Message Flow **

Security token services are designed to bridge the gap between security or technology realms. By transforming one token type into another, and at the same time transforming the claims inside those tokens, they are a great way to centralize authentication and authorization policies.

I plan on drilling deeper into these federated identity models in the future. For now I hope you'll trust me (pun intended) when I say that federated identity solutions are becoming more and more common, and you'll be able to fit into this model if you're building claims-aware applications today. Expect further innovation in this space in the future.

Until Next Time

Building claims-aware applications will prepare you for the future of identity on the Windows platform. For example, you'll be able to accept not only traditional token formats such as Windows credentials or X.509 certificates, but you'll also be ready to accept information cards. You'll also be better prepared to implement federated identity when the time comes for that.

The first step to learning about claims is to spend some time exploring System.IdentityModel, which is the new claims-based programming model introduced in the .NET Framework 3.0. Use the sample code with this column to get started and see how you can apply the power of claims-based identity in your own applications today.

Send your questions and comments for Keith to briefs@microsoft.com.

Keith Brown is a co-founder of Pluralsight, a premier Microsoft .NET training provider. Keith is the author of Pluralsight's Applied .NET Security course as well as several books, including The .NET Developer's Guide to Windows Security, which is available both in print and on the Web.