Advanced ASP.NET Programming for Windows Identity Foundation

  • 9/15/2010

Federation

At the beginning of the chapter, I introduced the Federation Provider and discussed some of the advantages that the IP-FP-RP pattern offers. The temptation to expand the architectural considerations about this important pattern is strong; however, here I want to keep the focus on WIF and give you a concrete coding example. There are many good high-level introductions to the topic you can refer to.

For a good introduction to the subject, refer to A Guide to Claims-Based Identity and Access Control by Dominick Baier, Vittorio Bertocci, Keith Brown, Matias Woloski, and Eugenio Pace (Microsoft Press, 2010).

WIF does not really care if the STS used by the RP is an IP-STS or an R-STS. Both types look the same in their metadata description and, despite the differences in the sequence that ultimately lead to that, they both issue a token as requested. It helps to see this in action in a concrete example.

Do you recall the first example we explored in Chapter 2? It was a classic RP-IP scenario, but it is very easy to transform it into a toy federation sample. Just right-click on the BasicWebSite_STS project in Solution Explorer, select the Add STS Reference entry, and use the wizard for creating yet another new STS project in the current solution.

Figure 4-7 shows the new solution layout.

Figure 4-7

FIGURE 4-7 BasicWebSite trusts BasicWebSite_STS, which in turn trusts BasicWebSite_STS_STS

Nothing changed for the RP, BasicWebSite, which is still outsourcing authentication to BasicWebSite_STS. BasicWebSite_STS was an IP-STS when we started, because it was an unmodified instance of the WIF STS template. After the wizard configured it to outsource authentication to BasicWebSite_STS_STS, however, BasicWebSite_STS became an R-STS; therefore, its login.aspx page will not be used anymore. If you run the solution you’ll observe the browser being redirected from BasicWebSite to BasicWebSite_STS, which will redirect right away to BasicWebSite_STS_STS, which will finally show its own login.aspx page. After you click Submit on the login form, the flow will go through the chain in the opposite order: BasicWebSite_STS_STS will issue a token that will be used for signing in BasicWebSite_STS, which in turn will issue a new token that will be used for signing in BasicWebSite. Figure 4-8 summarizes the sign-in flow.

Figure 4-8

FIGURE 4-8 The authentication flow linking BasicWebSite, BasicWebSite _STS, and BasicWebSite_STS_STS

  1. The user requests a page from BasicWebSite.

  2. Because the user is not authenticated, he is redirected to BasicWebSite_STS for authentication.

  3. BasicWebSite_STS itself outsources authentication to BasicWebSite_STS_STS; hence, it redirects the request accordingly

  4. Once the user successfully authenticates with BasicWebSite_STS_STS, he gets back a token.

  5. The user gets redirected back to BasicWebSite_STS, which validates the token from BasicWebSite_STS_STS and considers the user authenticated thanks to it.

  6. BasicWebSite_STS issues a token to the user, as requested.

  7. The user gets back to BasicWebSite with the token obtained from BasicWebSite_STS as required, and the authenticated session starts.

Convoluted? A bit, perhaps. On the upside, BasicWebSite is now completely isolated from the actual identity provider—changes in the IP will not affect the RP. If you have multiple RPs, you can now have them all trust the same R-STS, which will take care of enforcing any changes in the relationship with the IP (or IPs, as I’ll show in a moment) without requiring any ad-hoc intervention on the RP code or configuration itself. Pretty handy!

Transforming Claims

The example in the preceding section modified the authentication flow to conform to the federation pattern, but it didn’t really change the way in which BasicWebSite_STS processes claims. With its hard-coded claims entries, the default WIF STS template behavior mimics that of an IP-STS; whereas in its new FP role, BasicWebSite_STS is expected to process the incoming claims (in this case, from BasicWebSite_STS_STS). If you want to change BasicWebSite_STS into a proper R-STS, you need to modify the GetOutputClaimsIdentity method of the CustomSecurityTokenService class.

As you already know, in GetOutputClaimsIdentity the incoming claims are available in the IClaimsPrincipal principal parameter. You can pretty much do anything you want with the incoming claims, but I find it useful to classify the possible actions into three (non-exhaustive) categories: pass-through, modification, and injection of new claims. They are represented in step 5a of Figure 4-8. Here is a simple example of a GetOutputClaimsIdentity implementation that features all three methods:

  protected override IClaimsIdentity GetOutputClaimsIdentity
    (IClaimsPrincipal principal, RequestSecurityToken request, Scope scope )
  {
    if ( null == principal )
    {
      throw new ArgumentNullException( "principal" );
    }

    ClaimsIdentity outputIdentity = new ClaimsIdentity();

    IClaimsIdentity incomingIdentity = (IClaimsIdentity)principal.Identity;

    // Pass-through
    Claim nname = (from c in incomingIdentity.Claims
             where c.ClaimType == ClaimTypes.Name
             select c).Single();
    Claim nnnm = new Claim(ClaimTypes.Name, nname.Value, ClaimValueTypes.String, nname.
OriginalIssuer);
    outputIdentity.Claims.Add(nnnm);

    // Modified
    string rrole = (from c in incomingIdentity.Claims
            where c.ClaimType == ClaimTypes.Role
            select c.Value).Single();
    outputIdentity.Claims.Add(new Claim(ClaimTypes.Role, "Transformed " + rrole));

    // New
    outputIdentity.Claims.Add(new Claim("http://maseghepensu.it/hairlength",
                                        "a value", ClaimValueTypes.Double));

    return outputIdentity;
  }

Before going into the details of how the various transformations work, it is finally time to take a deeper look at that Claim class we’ve been using without giving it too much thought so far. Here are the various properties of the class and some methods of interest:

public class Claim
{
  // Methods
  public virtual Claim Copy();
  public virtual void SetSubject(IClaimsIdentity subject);
  // Properties

  public virtual string ClaimType { get; }
  public virtual string Issuer { get; }
  public virtual string OriginalIssuer { get; }
  public virtual IDictionary<string, string> Properties { get; }
  public virtual IClaimsIdentity Subject { get; }
  public virtual string Value { get; }
  public virtual string ValueType { get; }
}

One thing that immediately grabs your attention is that all properties of Claim are read-only: after the class has been created, the values cannot be changed. The only exception is the subject to which the Claim instance is referring to: SetSubject will change the value of the Subject property to a new IClaimsIdentity.

You are already familiar with Value and ClaimType because I’ve been using those throughout the entire book. ValueType is more interesting. It allows you to specify a type for the claim value, which the claim consumer can use to deserialize the claim in a common language runtime (CLR) type (or whatever type system your programming stack requires if you are not in .NET) other than the default string. That is a key enabler for applying complex logic to claims. Without knowing that DateOfBirth should be deserialized in a DateTime, you’ll find it difficult to verify whether it is below or above a given threshold. Note that the ValueType is just one indication: the Value returned by the claim is always a string regardless of the ValueType. You’ll have to call the appropriate Parse method (or similar) yourself.

The Properties dictionary is used for carrying extra information about the claim itself when the protocol requires it. For example, in SAML2 you might have properties such as SamlAttributeDisplayName assigned to a claim.

The Issuer property is a string representing the token issuer from which the claim has been extracted. The string itself comes from the mapping that IssuerNameRegistry makes between the certificate used for signing the token and the friendly name assigned to the associated issuer. The OriginalIssuer property records the first issuer that produced this claim in the federation chain. I’ve included more details about this in the “Pass-Through Claims” section.

Now that you understand a bit better how the Claim class works, let’s resume the discussion about the claim transformations.

Pass-Through Claims

One of the most common transformations you’ll want to apply to your claims is…no transformation at all. Sometimes the IP directly issues the claims the RP needs; hence, you have to make sure that those claims are reissued as-is by the R-STS.

Although the claim type and value come straight from the incoming values, the fact that the new claim is issued in a token signed by the R-STS makes the R-STS itself the asserting party and shadows the original issuer. The R-STS might even be accepting tokens from multiple issuers, which would complicate things further. There could be situations in which knowing the actual origin of the claim could change the way in which the information it carries is processed; therefore, it is important to somehow let the RP know which IP issued the claim in the first place. This is done by setting the OriginalIssuer property of the outgoing claim to the OriginalIssuer carried by the claim you are re-issuing. Here are the relevant lines from the GetOutputClaimsIdentity implementation shown earlier:

// Pass-through
    Claim nname = (from c in incomingIdentity.Claims
             where c.ClaimType == ClaimTypes.Name
             select c).Single();
    Claim nnnm = new Claim(ClaimTypes.Name, nname.Value, ClaimValueTypes.String, "," nname.
OriginalIssuer);
    outputIdentity.Claims.Add(nnnm);

In this example, the claim to be reissued is the Name claim. The code retrieves it from the incoming principal, and then it just creates a new claim that copies everything from the original except for the issuer. (Here the issuer parameter is left empty because it is going to be overridden with the current R-STS, anyway.) That snippet is designed to surface to you the use of OriginalIssuer, but in fact you can use a more compact form using Copy as shown here:

// Pass-through
    Claim nname = (from c in incomingIdentity.Claims
             where c.ClaimType == ClaimTypes.Name
             select c).Single();
    Claim nnnm = nname.Copy();
    outputIdentity.Claims.Add(nnnm);

Modifying Claims and Injecting New Claims

The distinction between modifying claims and injecting new claims is a bit philosophical, because from the code perspective the two transformations are the same.

Modifying a claim means producing a new claim by processing or combining the value of one or more incoming claims, according to arbitrary logic. An excellent example of that is given by the ADFS 2.0 claims-transformation language, which allows administrators to specify transformations without writing any explicit code. Of course, in GetOutputClaimsIdentity you can literally write whatever logic you want.

Injecting new claims usually entails looking up new information about the incoming subject—information that was not available to the IP but that the RP needs. A classic example is the buyer’s profile: imagine that the user is one employee, the IP is the user’s employer, and the RP is some kind of online shop. The R-STS might maintain information such as the last 10 items the user bought, data that the employer does not keep track of and that should be injected by the resource organization—for example, in the R-STS. The challenge here can be choosing which incoming claims should be used for uniquely identifying the current user and looking up his data in the R-STS profile store. Whereas the IP has one strong incentive to have such a unique identifier—because that is usually needed in order to apply the mechanics of the authentication method of choice—the R-STS does not have a similar requirement per se. The claims chosen should be unique, at least in the context of the current R-STS, and stable enough to be reusable across multiple transactions. The e-mail claim is a good example, but of course it’s not a perfect one because e-mail addresses do change from time to time—think of the situation where interns become full-time employees and similar events.

Home Realm Discovery

One of the great advantages of federation is the possibility of handling multiple identity providers without having to change anything in the RP itself. The Federation Providers can take care of all the trust relationships. Extending the audience of the application without paying any complexity price is great; however, the sheer possibility of using more than one IP does introduce a new problem: when an unauthenticated user shows up, which IP should she ultimately authenticate with? In the trivial federation case examined so far, the one with one FP and one IP, the answer is obvious: the redirect chain crawls all the way to the IP and back. When you have more than one IP, however, how does the R-STS decide if the redirect should go to IP A or IP B?

The problem of deciding which IP should authenticate the user is well known in literature, and it goes under the name of Home Realm Discovery (HRD). The HRD problem has many solutions, although as of today they are mostly ad hoc and what works in one given scenario might not be suitable for another. For example, one classic solution (offered out of the box by ADFS 2.0) asks the R-STS to show a Web page in which the user can pick his own realm among the list of all trusted IPs. This is often a good solution, but there are situations in which it is not advisable to reveal the list of all trusted IPs. Furthermore, sometimes asking the user to make a choice is inconvenient or unacceptable, in which case the IP selection should be done silently according to some criteria.

WS-Federation provides a parameter that can be useful in handling HRD: whr. It is meant to carry the address (or the urn: identifier) of the home realm. An R-STS receiving a wsignin1.0 message that includes whr will consider whr content to be the IP-STS of the requestor and will drive the sequence accordingly. (See Figure 4-9.)

Figure 4-9

FIGURE 4-9 The Home Realm Discovery problem

  1. The user requests a page from App.

  2. Because the user is not authenticated; instead, he is redirected to R-STS for authentication. The sign-in message includes a new parameter, whr, which indicates A as the home realm for the request.

  3. R-STS redirects the request to A.

  4. Once the user successfully authenticates with A, he gets back a token.

  5. The user gets redirected back to R-STS, which validates the token from A and considers the user authenticated thanks to it.

  6. R-STS issues a token to the user, as requested.

  7. The user gets back to App with the token obtained from R-STS as required, and the authenticated session starts.

Who injects the whr value in the authentication flow? There are at least two possibilities:

  • The requestor You can imagine a scenario in which the administrator of the organization of IP A gives to all users a link to the RP that already contains the whr parameter preselecting IP A. That is a handy technique, which eliminated the HRD problem at its root. Unfortunately, this is not guaranteed to work: this system requires the RP to understand (or at least preserve in the redirect to the R-STS) the whr parameter, but WS-Federation does not mandate this to the RP. In fact, RPs implemented via WIF do not support this behavior out of the box (although it’s not especially hard to add it).

  • The RP The RP itself could inject whr in the message to the R-STS. Imagine the case in which the RP is one specific instance of a multitenant application. In that case, the whr might be one of the parameters that personalize the instance for a given tenant. WIF supports this specific setup on the RP, by allowing you to specify the attribute homeRealm in the <federatedAuthentication/wsFederation> element of the WIF configuration. The value of homeRealm will be sent via whr to the R-STS. However, the WIF STS template project knows nothing about whr and will just ignore it. Once again, it is not hard to add some handling logic.

The R-STS is the recipient of whr. If the execution reaches the FP without having added a whr, it is up to the R-STS to make a decision on the basis of anything else that is available in the specific situation and can help decide which IP should be chosen.

Let’s once again set up a hypothetical solution in Visual Studio so that you can gain hands-on experience with the flow the scenario entails.

If you still have the solution we used for showing how federation works, right-click on BasicWebSite_STS, and again use the Add STS Reference Wizard to outsource its authentication to a new STS. Visual Studio will call the new STS BasicWebSite_STS_STS1. The current situation is described in Figure 4-10.

Figure 4-10

FIGURE 4-10 The sample solution showing how to handle HRD

BasicWebSite trusts BasicWebSite_STS, the R-STS of the scenario. BasicWebSite_STS now trusts BasicWebSite_STS_STS1 because with the latest add STS reference, its former trust relationship with BasicWebSite_STS_STS has been overridden. The goal here is to establish a mechanism that allows the flow to switch between the two IPs in the scenario (BasicWebSite_STS_STS and BasicWebSite_STS_STS1) dynamically.

The easiest thing to accomplish in the scenario is enabling the RP BasicWebSite to express a preference for one IP via whr. As mentioned earlier, this can be done easily via configuration:

<federatedAuthentication>
        <wsFederation passiveRedirectEnabled="true"
                      issuer="https://localhost/BasicWebSite_STS/"
                      realm="https://localhost/BasicWebSite/"
                      homeRealm="https://localhost/BasicWebSite_STS_STS/"
                      requireHttps="true" />
        <cookieHandler requireSsl="true" />
      </federatedAuthentication>

The value of homeRealm establishes that BasicWebSite_STS_STS should be used for authentication, which is contrary to what the WIF configuration of BasicWebSite_STS currently says. That way, it will be obvious whether the system successfully overrides the static settings.

The next step is making the WIF STS template understand whr. It is actually simple—it is mainly a matter of intercepting the redirect to the IP and forcing it to go whenever the whr decides. Add to the BasicWebSite_STS project a global.asax file. Here you can handle the WSFAM RedirectingToIdentityProvider event as follows:

<%@ Application Language="C#" %>
<%@ Import Namespace="Microsoft.IdentityModel.Web" %>

<script runat="server">
    void WSFederationAuthenticationModule_RedirectingToIdentityProvider
     (object sender, RedirectingToIdentityProviderEventArgs e)
    {
        string a = HttpContext.Current.Request.QueryString["whr"];
        if (a != null)
        {
            e.SignInRequestMessage.BaseUri = new Uri(a);
        }
    }

The code could not be easier. It verifies whether there is a whr parameter in the query string, and if it there is one, it assigns it to the BaseUri in the SignInRequestMessage, overwriting whatever value the BasicWebSite_STS configuration had put in there. As soon as the handler returns, the WSFAM will redirect the sign-in message to the whr—in this case, BasicWebSite_STS_STS. And that is exactly as you wanted it.

Having to specify the home realm in the RP configuration might be too static a behavior for many occasions. Fortunately, the RedirectingToIdentityProvider event can be easily handled on the RP as well, implementing any dynamic behavior. For example, you can think of maintaining a table of IP ranges where requests might come from, and map them to the corresponding IP addresses. For the sake of simplicity, here I’ll show you how to implement the approach when it is the requestor that sends the whr up front in its first request to the RP.

If you add a global.asax file to BasicWebSite, almost exactly the same code as shown earlier will give you the desired effect:

<%@ Application Language="C#" %>
<%@ Import Namespace="Microsoft.IdentityModel.Web" %>

<script runat="server">
    void WSFederationAuthenticationModule_RedirectingToIdentityProvider
            (object sender, RedirectingToIdentityProviderEventArgs e)
    {
        string a = HttpContext.Current.Request.QueryString["whr"];
        if (a != null)
        {
            e.SignInRequestMessage.HomeRealm = a;
         }
    }

The code here intercepts the execution right before sending back the redirect to the R-STS, and if the original request contained whr it ensures that it will be propagated to the R-STS as well. That means you can delete the homeRealm attribute in the BasicWebSite config, because now you have the ability to express whr directly at request time.

Step-up Authentication, Multiple Credential Types, and Similar Scenarios

The trick of using RedirectingToIdentityProvider for steering the request to the STS has many applications that go beyond the HRD problem examined earlier.

One eminent example of this shows up every time the RP needs to communicate some kind of preference about the authentication process the IP should use when issuing tokens to users. It’s great that claims-based identity decouples the RP from the authentication responsibilities, but there are situations in which the value of the operation imposes certain guarantees about the strength of the authentication. Imagine a banking Web site or a medical records Web site that gives access to certain operations only if the user is authenticated with a high-assurance method such as X.509 certificates or similar.

As you’ve grown to expect, WS-Federation has a parameter for that: wauth. It is supposed to be attached to wsignin1.0 messages to communicate to the STS the authentication method preference. Usually, the STS uses that for performing internal redirects to one endpoint that is secured with the corresponding authentication technique, or something to that effect (for example, wiring custom HttpHandlers or similar low-level tricks).

Each RP has its own criteria for assigning a value to wauth. Sometimes it is a blanket property for the entire Web site—in which case, it is expressed directly in <wsFederation> in the authenticationType attribute. At other times, the user is given the chance of selecting (directly or indirectly) from among multiple credential types. In yet another situation, there might be logic that silently establishes whether the current authentication level is enough for accessing the requested resource, or whether the system should step up to a higher level of assurance and re-authenticate the user accordingly. The last two cases call for a dynamic assignment of wauth, which is when reusing what you learned about whr and RedirectingToIdentityProvider comes in handy for wauth too.