다음을 통해 공유



August 2010

Volume 25 Number 08

Federated Identity - Passive Authentication for ASP.NET with WIF

By Michele Leroux | August 2010

The goal of federated security is to provide a mechanism for establishing trust relationships between domains so that users can authenticate to their own domain while being granted access to applications and services belonging to another domain. This makes authentication techniques like single sign-on possible, removes the need to provision and manage duplicate accounts for users across applications and domains, and significantly lowers the cost to extend applications to trusted parties.

In a federated security model, an Identity Provider (IdP) performs authentication and supplies a Security Token Service (STS) to issue security tokens. These tokens, in turn, assert information about the authenticated user: her identity and possibly other information including roles and more granular access rights. In a federated world, this information is referred to as claims, and claims-based access control is central to a federated security model. In this model, applications and services authorize access to features and functionality based on claims from trusted issuers (the STS).

Platform tools like Windows Identity Foundation (WIF) make it much easier to support this type of identity federation. WIF is an identity model framework for building claims-based applications and services, and for supporting SOAP-based (active) and browser-based (passive) federation scenarios. In the article “Claims-Based Authorization with WIF,” in the November 2009 issue of MSDN Magazine, I focused on using WIF with Windows Communication Foundation (WCF). In that article I described how to implement claims-based security models for WCF services and how to migrate to identity federation.

In this follow-up article I will focus on passive federation. I will explain the flow of communication for passive federation, show you several techniques for enabling federation in your ASP.NET applications, discuss claims-based authorization techniques for ASP.NET, and talk about single sign-on and single sign-out scenarios. Along the way, I will explain the underlying WIF features and components that support passive federation scenarios.

Passive Federation Basics

Passive federation scenarios are based on the WS-Federation specification. This describes how to request security tokens and how to publish and acquire federation metadata documents, which makes establishing trust relationships easy. WS-Federation also describes single sign-on and sign-out procedures and other federation implementation concepts.

While WS-Federation discusses many details about federation, there are sections devoted to browser-based federation that rely on HTTP GET and POST, browser redirects and cookies to accomplish the goal.

Some aspects of passive federation messaging are based closely on the WS-Trust specification. For example, passive federation employs a browser-compatible form of Request Security Token (RST) and RST Response (RSTR) when a security token is requested of an STS. In the passive federation scenario, I’ll call the RST a sign-in request message and the RSTR a sign-in response message. The WS-Trust specification focuses on SOAP-based (active) federation, such as between Windows clients and WCF services.

A simple passive federation scenario is illustrated in Figure 1.

image: A Simple Passive Federation Scenario

Figure 1 A Simple Passive Federation Scenario

Users authenticate to their domain and are granted access to a Web application according to their roles. The participants in this authentication scheme include the user (the subject), a Web browser (the requester), an ASP.NET application (the relying party or RP), an IdP responsible for authenticating the users within its domain and an STS belonging to the user’s domain (IP-STS). A sequence of browser redirects ensures that the user is authenticated at her domain prior to accessing the RP.

The user browses to the RP application (1) and is redirected to her IdP to be authenticated (2). If the user has not yet been authenticated at the IdP, the IP-STS may present a challenge or redirect her to a login page to collect credentials (3). The user supplies her credentials (4) and is authenticated by the IP-STS (5). At this point, the IP-STS issues a security token according to the sign-in request, and the sign-in response containing the token is posted to the RP via browser redirect (6). The RP processes the security token and authorizes access based on the claims it carries (7). If successfully authorized, the user is presented with the page she originally requested and a session cookie is returned (8).

Implementing this passive federation scenario with WIF and ASP.NET involves only a few steps:

  1. Establish a trust relationship between the RP and IdP (IP-STS)
  2. Enable passive federation for the ASP.NET application
  3. Implement authorization checks to control access to application features In the next sections I’ll discuss the features of WIF that support passive federation, walk through the steps to configure this simple scenario, and then explore other practical considerations for this and other scenarios.

WIF Features for Passive Federation

Before discussing implementation, let me review the features of WIF specifically useful for identity federation within your ASP.NET applications. To begin with, WIF supplies the following useful HTTP modules:

  • WSFederationAuthenticationModule (FAM): Enables browser-based federation, handling redirection to the appropriate STS for authentication and token issuance, and processing the resulting sign-in response to hydrate the issued security token into a ClaimsPrincipal to be used for authorization. This module also handles other important federation messages such as sign-out requests.
  • SessionAuthenticationModule (SAM): Manages the authenticated session by generating the session security token that contains the ClaimsPrincipal, writing it to a cookie, managing the lifetime of the session cookie and rehydrating the ClaimsPrincipal from the cookie when it’s presented. This module also maintains a local session token cache.
  • ClaimsAuthorizatonModule: Provides an extensibility point to install a custom ClaimsAuthorizationManager that can be useful for centralized access checks.
  • ClaimsPrincipalHttpModule: Creates a ClaimsPrincipal from the current user identity attached to the request thread. In addition, provides an extensibility point to install a custom ClaimsAuthenticationManager that can be useful for customizing the ClaimsPrincipal to be attached to the request thread.

ClaimsPrincipalHttpModule is most useful for applications without passive federation. You can think of it as a useful tool for implementing a claims-based security model in the ASP.NET application prior to moving to passive federation. I discussed this technique for WCF in my previous article.

The other three modules are typically used together for passive federation—although ClaimsAuthorizationModule is optional. Figure 2 illustrates how these core modules fit into the request pipeline and their functions in a typical federated authentication request.

image: WIF Components and HTTP Modules Engaged in Passive Federation

Figure 2 WIF Components and HTTP Modules Engaged in Passive Federation

Keeping in mind the flow of passive federation from Figure 1, when the user first browses to a protected page in the RP (1), access to the application will be denied. The FAM processes unauthorized requests, produces the sign-in message and redirects the user to the IP-STS (2). The IP-STS authenticates the user (3), produces a sign-in response that includes the issued security token, and redirects back to the RP application (4).

The FAM processes the sign-in response—ensuring that the response contains a valid security token for the authenticated user—and hydrates a ClaimsPrincipal from the sign-in response (5). This will set the security principal for the request thread and HttpContext. The FAM then uses the SAM to serialize the Claims­Principal to an HTTP cookie (6) that will be presented with subsequent requests during the browser session. If ClaimsAuthorizationModule is installed, it will invoke the configured ClaimsAuthorization­Manager, providing an opportunity to perform global access checks (7) against the ClaimsPrincipal prior to accessing the requested resource.

Once the requested resource is presented, access control can be implemented with traditional ASP.NET login controls, IsInRole checks and other custom code that queries the user’s claims (8).

On subsequent requests the session token is presented with the cookie previously written by the SAM (9). This time the SAM is engaged to validate the session token and rehydrate the Claims­Principal from the token (10). The FAM is not engaged unless the request is a sign-in response, a sign-out request, or if access is denied, which can happen if the session token is not present or has expired.

In addition to these modules, there are two ASP.NET controls that are also useful in passive federation:

  • FederatedPassiveSignIn Control: Can be used in lieu of the FAM if the application will redirect all unauthorized calls to a login page that hosts this control only when authentication is required. This assumes the user will interact with the sign-in process—useful in step-up authentication scenarios where the user is prompted for credentials, possibly additional credentials from the original login, as required by the application. The control handles redirection to the STS, processing the sign-in response, initializing the ClaimsPrincipal from the response and establishing a secure session by leveraging functionality exposed by the FAM and SAM.
  • FederatedPassiveSignInStatus Control: This control provides an interactive way for the user to sign in or sign out from the RP application, including support for federated sign-out.

Figure 3 illustrates how the flow of communication changes when the FederatedPassiveSignIn control is employed. The application relies on Forms authentication to protect resources and redirect to the login page, which hosts the control (1). The user clicks the FederatedPassiveSignIn control (or can be redirected to it automatically), which triggers a redirect to the STS (2). The control page receives the response from the STS, relying on the FAM and the SAM to process the sign-in response (3), hydrate the Claims­Principal and write the session cookie (4). When the user is redirected to the originally requested page (5), the SAM is engaged to validate the session cookie and hydrate the ClaimsPrincipal for the request. At this point, the ClaimsAuthorizationModule and that page can perform their authorization checks as illustrated in Figure 2.

image: Passive Federation with the FederatedPassive­-SignIn Control

Figure 3 Passive Federation with the FederatedPassive­-SignIn Control

Both the FAM and SAM rely on the appropriate Security­TokenHandler type to process incoming tokens. When a sign-in response arrives, the FAM iterates through SecurityTokenHandlerCollection looking for the correct token handler to read the XML token. In a federated scenario this will typically be Saml11Security­TokenHandler or Saml2Security­TokenHandler—though other token formats may be employed if you add custom token handlers. For the SAM, SessionSecurity­TokenHandler is used to process the session token associated with the session cookie.

Several identity model configuration settings are important to the flow of passive federation—and are used to initialize the FAM and the SAM and the FederatedPassiveSignIn control (although the latter also exposes properties configurable from the Visual Studio designer). Programmatically, you can supply an instance of the Service­Configuration type from the Microsoft.IdentityModel.Configuration namespace, or you can supply declarative configuration in the <microsoft.identityModel> section. Figure 4 summarizes identity model settings, many of which will be discussed in subsequent sections.

Figure 4 Summary of the Essential <microsoft.identityModel> Elements

Section Description
<issuerNameRegistry> Specify a list of trusted certificate issuers. This list is primarily useful for validating the token signature so that tokens signed by un-trusted certificates will be rejected.
<audienceUris> Specify a list of valid audience URIs for incoming SAML tokens. Can be disabled to allow any URI, though not recommended.
<securityTokenHandlers> Customize configuration settings for token handlers or supply custom token handlers to control how tokens are validated, authenticated, and serialized.
<maximumClockSkew> Adjust the allowed time difference between tokens and application servers for token validity. The default skew is 5 minutes.
<certificateValidation> Control how certificates are validated.
<serviceCertificate> Supply a service certificate for decrypting incoming tokens.
<claimsAuthenticationManager> Supply a custom ClaimsAuthenticationManager type to customize or replace the IClaimsPrincipal type to be attached to the request thread.
<claimsAuthorizationManager> Supply a custom ClaimsAuthorizationManager type to control access to functionality from a central component.
<federatedAuthentication> Supply settings specific to passive federation.

Enabling Passive Federation

WIF makes it easy to configure passive federation for your ASP.NET applications. An STS should supply federation metadata (as described in the WS-Federation specification) and WIF supplies a Federation Utility (FedUtil.exe), which uses federation metadata to establish trust between an RP and an STS (among other features useful to both active and passive federation scenarios). You can invoke FedUtil from the command line or from Visual Studio by right-clicking on the RP project and selecting Add STS reference.

You’ll complete the following simple steps with the FedUtil wizard:

  • The first page of the wizard allows you to confirm the configuration file to be modified by the wizard and the RP application URI.
  • The second page requests the path to the federation metadata XML document for the STS with which the RP will establish trust.
  • The third page allows you to supply a certificate to be used for decrypting tokens.
  • The final page shows a list of claims offered by the STS—which you can use to plan access control decisions, for example.

When the wizard steps are completed, FedUtil modifies the project to add a reference to the Microsoft.IdentityModel assembly. It also modifies the web.config to install the FAM and SAM modules and to supply identity model configuration settings for those modules. The application now supports passive federation and will redirect unauthorized requests to the trusted STS.

There’s an assumption here that the STS has prior knowledge of the RP, will thus issue tokens for authenticated users trying to access the RP, and of course that it has the public key the RP requires the STS to use to encrypt tokens. This is an easy way to get your ASP.NET applications initially set up for federation. Of course, it helps to understand how to set this up from scratch in case adjustments are required, and how to go beyond the basic settings enabled by the wizard. I’ll focus on the “from scratch” approach from here on in.

Without using FedUtil, you need to manually add a reference to the Microsoft.IdentityModel assembly, and manually configure the FAM and the SAM modules along with the necessary identity model settings. HTTP modules are added to two sections: system.web for Internet Information Services (IIS) 6 and system.webServer for IIS 7. Assuming the application is hosted in IIS 7, the WIF modules are configured as follows:

<modules>
  <!--other modules-->
  <add name="SessionAuthenticationModule" 
    type="Microsoft.IdentityModel.Web.SessionAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
    preCondition="managedHandler" />
  <add name="WSFederationAuthenticationModule" 
    type="Microsoft.IdentityModel.Web.WSFederationAuthenticationModule, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35" 
    preCondition="managedHandler" />
</modules>

By default this configuration will only protect resources with extensions explicitly mapped to be handled by the ASP.NET pipeline (.aspx, .asax, and so on). To protect additional resources with federated authentication, you should map those extensions to the ASP.NET pipeline in IIS, or you can set runAllManagedModulesForAllRequests to true in the modules setting (IIS 7 only) as follows:

<modules runAllManagedModulesForAllRequests="true">

For the FAM to kick in, you must also set the ASP.NET authentication mode to None and deny anonymous users access to application resources:

<authentication mode="None" />
<authorization>
  <deny users="?" />
</authorization>

Both modules rely on identity model configuration settings described in Figure 4, a typical example of which is shown in Figure 5. Most of these settings are generated for you by FedUtil, with the exception of certificateValidation and a few of the settings within federatedAuthentication. I typically recommend using PeerTrust certificate validation mode—which means that you explicitly add all trusted certificates, including that of the trusted issuer, to the local machine’s TrustedPeople store.

Figure 5 Identity Model Configuration for Passive Federation

<microsoft.identityModel>
  <service>
    <issuerNameRegistry type="Microsoft.IdentityModel.Tokens.ConfigurationBasedIssuerNameRegistry, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
      <trustedIssuers>
        <add thumbprint="EF38A0A6D1274766093D3D78BFE4ECA77C62D5C3" 
          name="https://localhost:60768/STS/" />
      </trustedIssuers>
    </issuerNameRegistry>
    <certificateValidation certificateValidationMode="PeerTrust" 
      revocationMode="Online" trustedStoreLocation="LocalMachine"/>
    <audienceUris>
      <add value="https://localhost:50652/ClaimsAwareWebSite2/" />
    </audienceUris>
    <federatedAuthentication>
      <wsFederation passiveRedirectEnabled="true" 
        issuer="https://localhost:60768/STS/" 
        realm="https://localhost:50652/ClaimsAwareWebSite2/" 
        requireHttps="true" />
      <cookieHandler requireSsl="true" name="FedAuth"  
        hideFromScript="true" path="/ClaimsAwareWebSite2" />
    </federatedAuthentication>
    <serviceCertificate>
      <certificateReference x509FindType="FindByThumbprint" 
        findValue="8A90354199D284FEDCBCBF1BBA81BA82F80690F2" 
        storeLocation="LocalMachine" storeName="My" />
    </serviceCertificate>
  </service>
 </microsoft.identityModel>

You should typically require HTTPS/SSL for passive federation to protect the issued bearer token from man-in-the-middle attacks, and require HTTPS/SSL for session cookies. By default, cookies are hidden from script, but it’s an important setting, which is why I call it out in Figure 5.

As for the name and path of the cookie, the name defaults to FedAuth, the path to the application directory. It can be useful to specify a unique name for the cookie, in particular if many RP applications in the solution share the same domain. Conversely, you can choose to specify a generic path when you want cookies to be shared across several apps on the same domain.

You will typically use FedUtil to configure your ASP.NET applications for passive federation using the FAM and SAM, then tweak the appropriate settings according to the requirements of the solution. You can also use the PassiveFederationSignIn control in lieu of the FAM as illustrated in Figure 3. The control can either load its settings from the microsoft.identityModel section, or you can set properties directly on the control.

The control approach is useful if you want unauthorized requests to be redirected to a login page where the user can explicitly sign in by clicking the control, rather than having the FAM automatically redirect to the STS. For example, if the user may belong to more than one identity provider (home realm), the login page could provide a mechanism for her to indicate her home realm prior to redirecting to the STS. I’ll discuss home realm discovery shortly.

Passive Token Issuance

As mentioned earlier, passive federation relies on HTTP GET and POST and browser redirects to facilitate communication between the RP and STS. Figure 6 shows the primary request parameters involved in the sign-in request and sign-in response during this process.

image: Primary Sign-In Request and Response Parameters Involved in Passive Federation Requests

Figure 6 Primary Sign-In Request and Response Parameters Involved in Passive Federation Requests

When the STS receives the sign-in request, it will verify that it knows about the RP by checking the wtrealm parameter against its list of known RP realms. Presumably the STS will have prior knowledge of the RP, the certificate required for token encryption, and any expectations with respect to the desired claims to be included in the issued token. The RP can indicate which claims it requires if it supplies the optional wreq parameter with a full sign-in request, and the STS can optionally respect that list or decide autonomously which claims to grant based on the authenticated user.

In a simple federation scenario like that described in Figure 1, there is a single RP and a single IP-STS responsible for authenticating users. If the IP-STS authenticates users against a Windows domain, it might issue role claims such as Admin, User or Guest. The assumption is that these roles have meaning to the RP for authorization. In the next section, I’ll assume these roles suffice and discuss authorization techniques. Following that I will discuss claims transformation at the RP to convert STS claims into something more useful for authorization as needed.

Claims-Based Authorization

As I discussed in my previous article, role-based security in the .NET Framework expects that a security principal is attached to each thread. The security principal, based on IPrincipal, wraps the identity of the authenticated user in an IIdentity implementation. WIF supplies ClaimsPrincipal and ClaimsIdentity types based on IClaimsPrincipal and IClaimsIdentity (which ultimately derive from IPrincipal and IIdentity). When the FAM processes the sign-in response, it hydrates a ClaimsPrincipal for the issued security token. Likewise, the SAM hydrates a ClaimsPrincipal for the session cookie. This ClaimsPrincipal is the heart of WIF authorization for your ASP.NET application.

You can use any of the following approaches to authorization:

  • Use location-specific authorization settings to restrict access to directories or individual application resources.
  • Use ASP.NET login controls, such as the LoginView control, to control access to functionality.
  • Use ClaimsPrincipal to perform dynamic IsInRole checks (for example, to dynamically hide or show UI elements).
  • Use the PrincipalPermission type to perform dynamic permission demands, or the PrincipalPermissionAttribute if declarative permission demand seems appropriate on a particular method.
  • Provide a custom ClaimsAuthorizationManager to centralize access checks in a single component, even prior to loading the requested resource.

The first three of these options rely on the IsInRole method exposed by the ClaimsPrincipal type. You must select a role claim type fitting for the IsInRole check so that the correct claims will be used to control access. The default role claim type for WIF is:

https://schemas.microsoft.com/ws/2008/06/identity/claims/role

If ClaimsPrincipal includes defined claims, the role claim type will match the default. Later, I will discuss permission claims in the context of claims transformation. When these are utilized, you should specify the permission claim type as the role claim type so that IsInRole will be effective.

You can control access to specific pages or directories globally from the web.config file. In the application root, supply a location tag specifying the path to protect, allow a list of acceptable roles and deny access to all other users. The following allows only Administrators to access files beneath the AdminOnly directory:

<location path="AdminOnly">
  <system.web>
    <authorization>
      <allow roles="Administrators" />
      <deny users="*"/>
    </authorization>
  </system.web>
</location>

As an alternative, you can put a web.config in any subdirectory and specify authorization rules. Placing the following configuration in the AdminOnly directory achieves the same result:

<configuration>
  <system.web>
    <authorization >
      <allow roles="Administrators" />
      <deny users="*"/>
    </authorization>
  </system.web>
</configuration>

To dynamically hide and show UI components or otherwise control access to features within a page, you can leverage the role-based features of controls such as the LoginView. However, most developers prefer to explicitly set control properties for access control during page load for more granular control. To do this, you can call the IsInRole method exposed by Claims­Principal. You can access the current principal through the Thread.CurrentPrincipal static property as follows:

if (!Thread.CurrentPrincipal.IsInRole("Administrators"))
  throw new SecurityException("Access is denied.");

Aside from explicit IsInRole checks at runtime, you can also write classic role-based permission demands using the Principal­Permission type. You initialize the type with the required role claim (the second constructor parameter), and when Demand is called, the IsInRole method of the current principal is called. An exception is thrown if the claim is not found:

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

This approach is useful for rejecting a request with an exception, when the appropriate roles aren’t present.

It’s also useful to centralize authorization checks common to all requested resources. Sometimes, if you have an access control policy—for example, rules stored in a database—you can use a central component to read those rules to control access to features and functionality. For this, WIF supplies a ClaimsAuthorizationManager component that you can extend. Recall from my previous article that you can configure this type of custom component in the identity model section:

<microsoft.identityModel>
  <service>
    <!--other settings-->
    <claimsAuthorizationManager 
      type="CustomClaimsAuthorizationManager"/>
  </service>
</microsoft.identityModel>

Figure 7illustrates a custom ClaimsAuthorizationManager that verifies the presence of the name claim and whether the requested resource is within the AdminsOnly directory requires the Administrators role claim.

Figure 7 Custom ClaimsAuthorizationManager Implementation

public class CustomClaimsAuthorizationManager: 
  ClaimsAuthorizationManager {
  public CustomClaimsAuthorizationManager()
  { }
  public override bool CheckAccess(
    AuthorizationContext context) {
    ClaimsIdentity claimsIdentity = 
      context.Principal.Identity as ClaimsIdentity;
    if (claimsIdentity.Claims.Where(
      x => x.ClaimType == ClaimTypes.Name).Count() <= 0)
      throw new SecurityException("Access is denied.");
        
    IEnumerable<Claim> resourceClaims = 
      context.Resource.Where(x=>x.ClaimType==ClaimTypes.Name);
    if (resourceClaims.Count() > 0) {
      foreach (Claim c in resourceClaims) {
        if (c.Value.Contains("\AdminOnly") && 
          !context.Principal.IsInRole("Administrators"))
          throw new SecurityException("Access is denied.");
      }
    }
    return true;
  }
}

The CustomClaimsAuthorizationManager overrides Check­Access to provide this functionality. This method supplies an AuthorizationContext parameter, which provides information about the request action (for passive federation this is an HTTP verb such as GET or POST), the requested resource (a URI), and the Claims­Principal, which is not yet attached to the request thread.

Claims Transformation

Often, the claims issued by the IP-STS, although useful for describing the authenticated user, are not relevant to the authorization requirements of the RP. It isn’t the IdP’s job to know what type of roles, permissions or other fine-grained artifact is necessary for authorization at each RP. It’s the IdP’s job to grant claims that are relevant to the identity provider domain, claims that the IdP can assert about the authenticated user.

As such, the RP may need to transform claims from the IP-STS into something more relevant for authorization. This implies that the RP may map the user identity (perhaps by user name or UPN) to a set of RP claims. Assuming the IP-STS grants default role claims, Figure 8 lists a possible set of permission claims that the RP could issue based on each incoming role claim. The permission claim type may be a custom claim type defined by the RP such as:

urn:ClaimsAwareWebSite/2010/01/claims/permission

A good place to transform incoming IP-STS claims is with a custom ClaimsAuthenticationManager. You can install a custom ClaimsAuthenticationManager by adding the following to the microsoft.identityModel section:

<microsoft.identityModel>
  <service>
    <!--other settings-->
    <claimsAuthenticationManager 
      type="CustomClaimsAuthenticationManager"/>
  </service>
</microsoft.identityModel>

Figure 9shows a sample CustomClaimsAuthenticationManager that transforms incoming role claims granted by the IP-STS into permission claims relevant to the RP.

Figure 8 Transforming Role Claims to Permission Claims at the RP

Role Claim Permission Claims
Administrators Create, Read, Update, Delete
Users Create, Read, Update
Guest Read

Figure 9 Custom Claims Transformation at the RP

public class CustomClaimsAuthenticationManager: 
  ClaimsAuthenticationManager {
  public CustomClaimsAuthenticationManager() { }
  public override IClaimsPrincipal Authenticate(
    string resourceName, IClaimsPrincipal incomingPrincipal) {
    IClaimsPrincipal cp = incomingPrincipal;
    ClaimsIdentityCollection claimsIds = 
      new ClaimsIdentityCollection();
    if (incomingPrincipal != null && 
      incomingPrincipal.Identity.IsAuthenticated == true) {
      ClaimsIdentity newClaimsId = new ClaimsIdentity(
        "CustomClaimsAuthenticationManager", ClaimTypes.Name, 
        "urn:ClaimsAwareWebSite/2010/01/claims/permission");
      ClaimsIdentity claimsId = 
        incomingPrincipal.Identity as ClaimsIdentity;
      foreach (Claim c in claimsId.Claims)
        newClaimsId.Claims.Add(new Claim(
          c.ClaimType, c.Value, c.ValueType, 
          "CustomClaimsAuthenticationManager", c.Issuer));
      if (incomingPrincipal.IsInRole("Administrators")) {
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Create"));
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Read"));
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Update"));
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Delete"));
      }
      else if (incomingPrincipal.IsInRole("Users")) {
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Create"));
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Read"));
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Update"));
      }
      else {
        newClaimsId.Claims.Add(new Claim(
          "urn:ClaimsAwareWebSite/2010/01/claims/permission", 
          "Read"));
      }
      claimsIds.Add(newClaimsId);
      cp = new ClaimsPrincipal(claimsIds);
    }
    return cp;
  }
}

For IsInRole checks (as described earlier) to work, you must provide the permission claim type as the role claim type. In Figure 9, this is specified when the ClaimsIdentity is constructed because the RP is creating the ClaimsIdentity.

In the case where incoming SAML tokens are the source of claims, you can provide the role claims type to the SecurityTokenHandler. The following illustrates how to declaratively configure the Saml11SecurityTokenHandler to use the permission claim type as the role claim type:

<microsoft.identityModel>
  <service>
    <!--other settings-->
    <securityTokenHandlers>
      <remove type="Microsoft.IdentityModel.Tokens.Saml11.Saml11SecurityTokenHandler, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35"/>
      <add type="Microsoft.IdentityModel.Tokens.Saml11.Saml11SecurityTokenHandler, Microsoft.IdentityModel, Version=3.5.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35">
        <samlSecurityTokenRequirement >
          <roleClaimType 
            value= "urn:ClaimsAwareWebSite/2010/01/claims/permission"/>
        </samlSecurityTokenRequirement>
      </add>
    </securityTokenHandlers>
  </service>
</microsoft.identityModel>

SAML token handlers have a samlSecurityTokenRequirement section where you can provide a setting for the name and role claim type, along with other settings related to certificate validation and Windows tokens.

Home Realm Discovery

So far, I have focused on a simple federation scenario with a single IP-STS. The assumption is that the RP will always redirect to a particular IP-STS to authenticate users.

In the world of federation, however, the RP may trust multiple token issuers from several domains. A new challenge presents itself in this case because the RP must decide which IP-STS should authenticate users requesting access to resources. The domain to which users authenticate is known as the user’s home realm, and thus this process is called home realm discovery.

There are a number of mechanisms an application may use for home realm discovery:

  • As in the current example, the home realm is known in advanced and so requests are always redirected to a particular IP-STS.
  • Users may browse to the RP from another portal, which can provide a query string to indicate the home realm for users from that portal.
  • The RP may require that users land on a particular entry page for each home realm. The landing page could assume a particular home realm.
  • The RP may be able to determine the home realm by the IP address of the request or some other heuristic.
  • If the RP can’t determine the home realm from one of the aforementioned techniques, it can present a UI where the user can select the home realm or provide information that helps the RP determine this.
  • If the RP supports information cards, the selected card can drive authentication to the appropriate home realm using active federation.
  • The WS-Federation briefly describes how one might implement a discovery service for resolving the home realm, but there isn’t a well-defined specification for this.

No matter how the home realm is discovered, the goal is to redirect the user to authenticate with the correct IP-STS. There are a few possible scenarios here. In one scenario, the RP may need to dynamically set the issuer URI so that the sign-in request is sent to the correct IP-STS. In this case, the RP must list all trusted IP-STS in the trustedIssuers section, for example:

<trustedIssuers>
  <add thumbprint="6b887123330ae8d26c3e2ea3bb7a489fd609a076" 
    name="IP1" />
  <add thumbprint="d5bf17e2bf84cf2b35a86ea967ebab838d3d0747" 
    name="IP2" />
</trustedIssuers>

In addition, you can override the RedirectingToIdentityProvider event exposed by the FAM and, using relevant heuristics, determine the correct URI for the STS. To do this, place the following code in the Global.asax implementation:

void WSFederationAuthenticationModule_RedirectingToIdentityProvider(
  object sender, RedirectingToIdentityProviderEventArgs e) {
  if (e.SignInRequestMessage.RequestUrl.Contains(
    "IP1RealmEntry.aspx")) {
    e.SignInRequestMessage.BaseUri = 
      new Uri("https://localhost/IP1/STS/Default.aspx");
  }
  else if (e.SignInRequestMessage.RequestUrl.Contains(
    "IP2RealmEntry.aspx")) {
    e.SignInRequestMessage.BaseUri = new Uri(
       "https://localhost/IP2/STS/Default.aspx");
  }
}

The other scenario involves passing the home realm parameter (whr) with the sign-in request to the primary STS. The RP may, for example, have a Resource STS (R-STS or RP-STS) responsible for claims transformation. The RP-STS doesn’t authenticate users (it’s not an IdP), but it has trust relationships with one or more other IdPs.

The RP has a trust relationship with the RP-STS, and will always respect tokens issued by the RP-STS. The RP-STS is responsible for redirecting to the correct IdP for each request. The RP-STS can determine the correct IP-STS to redirect to as in the code just described, but another option is for the RP to supply information about the home realm, passing this in the home realm parameter to the RP-STS. In this case, the RP dynamically sets the home realm parameter:

void WSFederationAuthenticationModule_RedirectingToIdentityProvider(
  object sender, RedirectingToIdentityProviderEventArgs e) {
  if (e.SignInRequestMessage.RequestUrl.Contains(
    "IP1RealmEntry.aspx")) {
    e.SignInRequestMessage.HomeRealm = 
      "https://localhost/IP1/STS/Default.aspx";
  }
  else if (e.SignInRequestMessage.RequestUrl.Contains(
    "IP2RealmEntry.aspx")) {
    e.SignInRequestMessage.HomeRealm = 
      "https://localhost/IP2/STS/Default.aspx";
  }
}

The RP-STS uses this parameter to redirect to the correct IP-STS and subsequently transforms claims from the IP-STS into claims relevant to the RP.

Single Sign-On and Single Sign-Out

Single sign-on and single sign-out are important parts of federation. Single sign-on is a feature that allows authenticated users to access multiple RP applications while authenticating only once. Single sign-out, as it implies, facilitates sign-out from all RP applications and any relevant STS chain with a single request.

In a simple federation scenario like that shown in Figure 1, the user authenticates to the IP-STS and is authorized at the RP based on the issued security token. Post-authentication, the user has a session cookie for the STS and another for the RP. Now, if the user browses to another RP, she will be redirected to the IP-STS for authentication—assuming both RP applications trust the same IP-STS. Because the user already has a session with the IP-STS, the STS will issue a token for the second RP without prompting for credentials. The user now has access to the second RP and has a new session cookie for the second RP.

As I’ve discussed, WIF supplies the SAM to write out the session cookie for authenticated users. By default, this session cookie is issued for the relative application address for the domain, and its base name is FedAuth. Because federated session cookies can be large, the token is usually split into two (or more) cookies: FedAuth, FedAuth1, and so on.

If you are hosting more than one application at the same domain, as part of the federation scenario, the default behavior would be that the browser has a FedAuth cookie for each RP (see Figure 10). The browser sends only those cookies associated with the domain and path for the request.

image: Session Cookies Associated with Each RP and the STS

Figure 10 Session Cookies Associated with Each RP and the STS

This default behavior is generally fine, but sometimes it’s necessary to supply a unique, per-application name for each session cookie—in particular if they’re hosted on the same domain. Or multiple applications at the same domain may share a session cookie, in which case you can set the cookie path to “/”.

If the session cookie expires, the browser will remove it from the cache and the user will be redirected once again to the STS for authentication. Separately, if the issued token associated with the session cookie has expired, WIF will redirect to the STS for a new token.

Sign-out is more explicit—usually driven by the user. Single sign-out is an optional feature of the WS-Federation specification that suggests the STS should also notify other RP applications for which it has issued tokens of the sign-out request. This way, the session cookie is removed for all applications the user browsed to during the single sign-on session. In a more complex scenario, where multiple STSs are involved, the primary STS receiving the sign-out request should also notify other STSs to do the same.

For the purpose of this discussion, I will focus on what you should do at the RP to facilitate federated single sign-out. You can place the FederatedPassiveSignInStatus control on any page from which you want to support sign-in and sign-out and the control will automatically indicate its state. Once signed-in, the control presents a link, button or image for signing out.

When you click the control, it will handle sign-out according to the SignOutAction property, which can be Refresh, Redirect, RedirectToLoginPage or FederatedPassiveSignOut. The first three delete the session cookie for the application, but do not notify the STS of the sign-out request. When you select the FederatedPassiveSignOut setting, the control will call SignOut on WSFederationAuthenticationModule. This ensures that federated session cookies are removed for the application. In addition, a sign-out request is sent to the STS:

GET https://localhost/IP1/STS?wa=wsignout1.0

If you aren’t using the FederatedPassiveSignInStatus control, you can directly call WSFederationAuthenticationModule.SignOut to trigger a redirect to the STS with the sign-out request.

Single sign-out implies that the user is signed out of all applications she signed into with her federated identity. If the STS supports this, it should hold a list of RP applications the user logged in to during her session, and issue a clean-up request to each RP when federated sign-out is requested:

GET https://localhost/ClaimsAwareWebSite?wa=wsignoutcleanup1.0

In more complex scenarios, the same clean-up request should be sent to any other STS involved in the federated session. To that end, the STS would have to have prior knowledge of the clean-up URI for each RP and STS. To support single sign-out, your RPs should be able to process these clean-up requests. Both the FAM and the FederatedPassiveSignInStatus control support this. If you’re using the FAM, the clean-up request can be posted to any URI at the RP and the FAM will process the request and clean up any session cookies. If you’re using the FederatedPassiveSignInStatus control, the clean-up request must be posted to a page that contains the control.

In fact, the WS-Federation specification does not detail how to implement single sign-out and clean-up behavior beyond the recommended query strings and flow of communication. It’s not easy to guarantee single sign-out will be effective across all federation partners as a result—but if you own the environment and want to achieve this goal, it’s indeed possible.


Michele Leroux Bustamante is chief architect at IDesign (idesign.net) and chief security architect at BiTKOO (bitkoo.com). She’s also a Microsoft regional director for San Diego and a Microsoft MVP for Connected Systems.

Thanks to the following technical expert for reviewing this article: Govind Ramanathan