Chapter 9. Federated Authentication in WCF

WHAT'S IN THIS CHAPTER?

  • Understanding the federated authentication principles

  • Getting started with Windows Identify Foundation (WIF)

  • Implementing a Security Token Service with WIF

  • Implementing a Claim-Aware service

This chapter is exclusively dedicated to the integration between the Windows Identity Foundation framework and WCF, mainly focusing on how to negotiate claims from a secure token service and use it for security decisions in the services.

If you are not experienced with federated authentication, do not worry, as important aspects are discussed here about how this security model works under the scene in WCF.

FEDERATED AUTHENTICATION

Federated authentication is another example of Brokered Authentication, where services rely on a third party, a Security Token Service (STS), for authenticating callers and issuing security tokens that carry claims describing the caller.

An STS in this context provides a powerful mechanism to meet some of the following requirements:

  • Decouple services from different authentication mechanisms or credential types so they can focus on authorizing or processing relevant claims.

  • Support a federated architecture where clients authenticated in one domain are granted access to resources or services in another domain by establishing trust between each domain's STS.

  • Transform claims into a relevant set of claims expected by the authorization code at services.

As you can see, it represents an excellent tool for consolidating the client authentication, and gets rid of the identity silos that have been created by different applications in the enterprise.

What Is a Security Token Service (STS)?

From a high level, a STS is service implementation that primarily follows the WS-Trust specification to issue security tokens. The WS-Trust is more than that, as it describes a contract with four operations — Issue, Validate, Renew, and Cancel. These operations are called by clients to request a new security token, to validate an existing token, to renew an expired token, or to cancel a token when it is no longer needed.

Figure 9-1 summarizes the complete process for consuming a service that delegates the client authentication to a STS.

Security Token Service

Figure 9.1. Security Token Service

  1. The client application sends a RequestSecurityToken message (RST), which is part of the WS-Trust specification, to request a new security token to the STS. This message contains client credentials, and it is protected with security settings previously agreed on between the client and the STS.

  2. The STS receives the request message, authenticates the client, and verifies the message protection. If the client authentication and message protection verification give positive results, the STS uses information received in the message to create a session key and a new security token that includes an encrypted version of the session key (encrypted with a key that only the service possesses). The security token and the session key are attached to a RequestSecurityTokenResponse message (RSTR) and are sent back to the client.

  3. The client receives the RSTR message and extracts the security token and the session key from that message. After that, it includes the security token as client credentials for the request message to service, and protects that message using the session key.

  4. The service receives the request from the client, verifies that the security token used as client credentials is valid, and decrypts and extracts the session key from that token (using the same service key used by the STS). Finally, the session key is used to verify the message protection. If everything is okay, the service operation is executed and the results are sent back to the client.

Federation Authentication Between Multiple Domains

A simple STS works fine as long as all involved parties (clients, services, and the STS) live in the same trust boundary, which typically receives the name of domain or security realm.

The thing becomes complex when a client running in a specific domain wants to consume a service exposed in another domain. This problem is typically solved by establishing a trust relationship between the STSes running on each domain. (See Figure 9-2.)

Federated authentication

Figure 9.2. Federated authentication

In the way this works, a client in domain A should first obtain a security token from the STS in the same domain, and use it later for obtaining a security token from the STS running on domain B. Because there is a trust relationship between the STS running in both domains, the client should be able to get a security token from the domain B without problems, and use it for consuming any service exposed in that domain.

Everything is transparent for the client applications and the services. The client applications always authenticate against the first STS using the same credentials (which is usually known as Single Sign On or just SSO), and the services always receive a set of claims representing identity information about the clients.

The same example could be expanded to multiple domains by chaining multiple STSes to create a trust network.

Security Assertions Markup Language (SAML)

Discussed so far has been how a client application can obtain a security token from a STS to be used with a specific service. As you might notice, that security token is something abstract and it can be represented with any of the security tokens that we already know. These include usernames, X509 certificates, or Kerberos. However, there is a security token that you haven't seen yet and it represents the best way to carry user claims in scenarios with federated authentication. Enter SAML to the scene.

SAML is the acronym for Security Assertions Markup Language, a vendor-neutral specification for exchanging units of security-related information called assertions — equivalent to an identity claim.

Four main factors have contributed in the adoption of SAML as a security token for federated authentication scenarios:

  1. It is completely XML-based, and therefore assures interoperability between different platforms.

  2. It contains a payload section totally extensible through the use of custom attributes or assertions. As said, an assertion can carry information about an identity claim.

  3. It can carry cryptographic information to perform encryption or digital signature operations, which in essence are required for proving message protection.

  4. It can carry a digital signature created by the issuer of the token. This signature is extremely useful for two purposes: it allows the services to verify that the token comes from a trusted party, and it has not been changed in transit. (Otherwise, the signature cannot be verified with unchanged data in a token. The signature cannot be verified if the content of the SAML token was changed.)

Nowadays, there are two available specifications for SAML; 1.1 and 2.0.

The following XML fragment illustrates how a SAML token 1.1 looks when it is included in a SOAP message:

<saml:Assertion MajorVersion="1" MinorVersion="1"
AssertionID="_5d1920bc-3efa-481a-99e2-1a9469f1a128"
Issuer="http://WCFBookSTS" IssueInstant="2009-08-13T14:00:37.716Z"
xmlsn:saml="urn:oasis:names:tc:SAML:1.0:assertion">
    <saml:Conditions NotBefore="2009-08-13T14:00:37.651Z"
NotOnOrAfter="2009-08-14T00:00:37.651Z">
      <saml:AudienceRestrictionCondition>
        <saml:Audience>http://localhost:8000/EchoClaims</saml:Audience>
      </saml:AudienceRestrictionCondition>
    </saml:Conditions>
    <saml:AttributeStatement>
      <saml:Subject>
        <saml:SubjectConfirmation>
          <saml:ConfirmationMethod>urn:oasis:names:tc:SAML:1.0:cm:holder-of-key
</saml:ConfirmationMethod>
          <KeyInfo xmlns="http://www.w3.org/2000/09/xmldsig#">
            <!-- Encrypted Session Key -->
          </KeyInfo>
        </saml:SubjectConfirmation>
      </saml:Subject>
      <saml:Attribute AttributeName="name"
AttributeNamespace="http://schemas.xmlsoap.org/ws/2005/05/identity/claims">
        <saml:AttributeValue>
          <!-- Attribute value -->
</saml:AttributeValue>
      </saml:Attribute>
      <saml:Attribute AttributeName="AgeClaim"
AttributeNamespace="http://WCFBookSamples/2008/05">
        <saml:AttributeValue>
          <!-- Attribute value -->
        </saml:AttributeValue>
      </saml:Attribute>
    </saml:AttributeStatement>
    <ds:Signature xmlns:ds="http://www.w3.org/2000/09/xmldsig#">
      <!-- Issuer Signature -->
    </ds:Signature>
  </saml:Assertion>

WINDOWS IDENTITY FOUNDATION (WIF)

The Windows Identity Foundation (WIF) framework, formerly called Geneva framework, is the name for the new framework for building claims-based applications and services, and for implementing federated security scenarios. Included among this framework's features is focusing on the development of a Security Token Service, and its close integration with WCF.

Claims-Based Model in WIF

Although WCF has native support for a claims-based security model, WIF enhances this experience by simplifying access to claims at runtime and providing a mechanism to support claims-based authorization using the role-based authorization principles already available in the .NET Framework. In other words, all the classes shipped as part of WCF to implement claims-based security, already discussed throughout this chapter, are no longer needed if you decide to develop your security authorization model on top of WIF.

Note

All the classes discussed in the chapter are part of the WIF runtime. There is also an optional SDK that provides additional tools, documentation, and samples that are very useful for any person developing applications targeting the WIF runtime.

A claim in WIF is represented by the class Microsoft.IdentityModel.Claims.Claim:

public class Claim
{
  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; }
}

The properties ClaimType, Issuer, and OriginalIssuer have the same meaning as we saw in the traditional WCF model. The property Value, on the other hand, presents a slight difference as it can only contain string values instead of an object reference that could point to any CLR type. So an integer value of 10 would be represented as "10". The property ValueType has been added to figure out how to deserialize the value of the claim by telling you the format of the value. Some of the possible value types are available in the class Microsoft.IdentityModel.Claims.ClaimValueTypes:

public static class ClaimValueTypes
{
    public const string
       Base64Binary = "http://www.w3.org/2001/XMLSchema#base64Binary";
    public const string
       Boolean = "http://www.w3.org/2001/XMLSchema#boolean";
    public const string
       Date = "http://www.w3.org/2001/XMLSchema#date";
    public const string
       Datetime = "http://www.w3.org/2001/XMLSchema#dateTime";
    public const string
       DaytimeDuration = "http://www.w3.org/TR/2002/WD-xquery-operators-
20020816#dayTimeDuration";
    public const string
       Double = "http://www.w3.org/2001/XMLSchema#double";
    public const string
       DsaKeyValue = "http://www.w3.org/2000/09/xmldsig#DSAKeyValue";
    public const string
       HexBinary = "http://www.w3.org/2001/XMLSchema#hexBinary";
    public const string
       Integer = "http://www.w3.org/2001/XMLSchema#integer";

}

The property Subject references a valid instance of Microsoft.IdentityModel.Claims.IClaimsIdentity, a new interface derived from System.Security.IIdentity to represent the identity of the subject that owns the claim.

And finally, in case you want to include additional data about the claim that cannot be represented by any of the existing properties, you can use the Properties dictionary for that purpose. This dictionary is a generic property bag for storing key/value pairs.

The New IClaimsIdentity and IClaimsPrincipal Interfaces

WIF introduced two new interfaces for representing client identities and principals: Microsoft.IdentityModel.Claims.IClaimsIdentity and Microsoft.IdentityModel.Claims.IClaimsPrincipal respectively (and the corresponding implementations ClaimsIdentity and ClaimsPrincipal).

IClaimsIdentity, in addition to giving you information about the client identity, provides a Claims property to gain access to claims associated to that identity. This could be the name, whether it is authenticated, or the way the client was authenticated.

public interface IClaimsIdentity : IIdentity
{
  ClaimCollection Claims { get; }
}

Because you have a new identity interface, there is also a principal interface IClaimsPrincipal with the corresponding implementation ClaimsPrincipal. This specific implementation wraps an instance of IClaimsIdentity instance and implements the IsInRole method by checking into the available claims:

public interface IClaimsPrincipal : IPrincipal
{
  ClaimsIdentityCollection Identities { get; }
}

Building an Active STS

Prior to WIF, building an active STS using WCF was a very complicated thing. All the documentation and examples available were unofficial and spread across different blogs. WIF fulfills an important missing piece in this area, providing all the necessary components and necessary classes to implement a STS and introduce claims-based security in your applications.

The core functionality to build a custom STS in WCF is provided by the abstract class Microsoft.IdentityModel.SecurityTokenService.SecurityTokenService.

A custom STS inherits this class and provides at least the following functionality:

  1. A constructor that receives a SecurityTokenServiceConfiguration instance to configure some specific features of the STS:

    public class MySecurityTokenService : SecurityTokenService
    {
      public MySecurityTokenService( SecurityTokenServiceConfiguration configuration )
        : base( configuration )
      {
      }
    }

    SecurityAuthenticationFederatedSTSMySecurityTokenService.cs

  2. An implementation of the GetScope method to validate whether a security token can be provided for the service that the client wants to consume, and to supply two appropriate credentials for encrypting and signing the token:

    protected override Scope GetScope( IClaimsPrincipal principal,
      RequestSecurityToken request )
    {
        if (request.AppliesTo == null)
        {
            throw new InvalidRequestException("The appliesTo is null.");
        }
    
        if (!request.AppliesTo.Uri.Equals(new Uri("http://localhost:8000/EchoClaims")))
    {
            Console.WriteLine("The relying party address is not valid. ");
            throw new InvalidRequestException(String.Format(
                "The relying party address is not valid. Expected value is {0},
    the actual value is {1}.",
                "http://localhost:8000/EchoClaims",
                request.AppliesTo.Uri.AbsoluteUri));
        }
        X509Certificate2 serviceCertificate = CertificateUtil.GetCertificate(
            StoreName.My,
            StoreLocation.LocalMachine,
            "CN=WCFServer");
    
        X509EncryptingCredentials encryptingCredentials =
            new X509EncryptingCredentials(serviceCertificate);
    
        Scope scope = new Scope(request.AppliesTo.Uri.AbsoluteUri,
            SecurityTokenServiceConfiguration.SigningCredentials,
            encryptingCredentials );
    
        return scope;
    }

    SecurityAuthenticationFederatedSTSMySecurityTokenService.cs

    The previous code validates AppliesTo member of the request message, which identifies the service that consumes the token (http://localhost:8000/EchoClaims), and provides a certificate with a subject name equal to "CN=WCFServer" for encrypting the SAML token, and a certificate available in the configuration for signing it.

  3. An implementation of the GetOutputClaimsIdentity method to supply claims for the resulting security token:

    protected override IClaimsIdentity GetOutputClaimsIdentity(IClaimsPrincipal
      principal, RequestSecurityToken request, Scope scope)
    {
      IClaimsIdentity callerIdentity = (IClaimsIdentity)principal.Identity;
      IClaimsIdentity outputIdentity = new ClaimsIdentity();
    
      Claim nameClaim = new Claim(ClaimTypes.Name, callerIdentity.Name);
    
      Claim ageClaim = new Claim(
            "http://WCFBookSamples/2008/05/AgeClaim",
            "25",
            ClaimValueTypes.Integer );
    
      outputIdentity.Claims.Add( nameClaim );
      outputIdentity.Claims.Add( ageClaim );
    
      return outputIdentity;
    }

    SecurityAuthenticationFederatedSTSMySecurityTokenService.cs

The methods GetScope and GetOutputClaimsIdentity receive as argument a valid instance of an IClaimsPrincipal class that represents the authenticated caller's identity. This identity is typically used to determine the appropriate class to grant the caller. In the previous example, two claims are added to the security token, a claim for representing the caller name, and another hard-coded claim for representing the age.

After you have the service implementation ready, you need to expose it with one or more endpoints as you normally would with a traditional WCF service. In the case of a STS, you need to expose one of the contracts provided by WIF, which mainly depends on the features and WS-Trust version that you want to support in the STS.

WIF includes four contracts in the Microsoft.IdentityModel.Protocols.WSTrust namespace: IWSTrust13SyncContract for exposing a WS-Trust endpoint with the latest version of the specification (1.3), IWSTrustFeb2005SyncContract for exposing an older version of the specification (February 2005), or the asynchronous versions of the same contracts IWSTrust13AsyncContract and IWSTrustFeb2005AsyncContract.

The service class that implements the four contracts, WSTrustServiceContract, is also available in the same namespace. The following example illustrates one possible configuration for exposing a WS-Trust endpoint (the sync version of a WS-Trust 1.3 specification) using this service implementation:

<services>
  <service name="Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceContract"
           behaviorConfiguration="stsBehavior">
    <endpoint address="WCFBookSTS"
              contract=
               "Microsoft.IdentityModel.Protocols.WSTrust.IWSTrust13SyncContract"
              binding="ws2007HttpBinding"
              bindingConfiguration="stsBinding"/>

  </service>
</services>

You basically need to define as many endpoints as authentication types and WS-Trust features you want to support. The binding definition for the previous endpoint "stsBinding" could be the following if you want to support username authentication for that endpoint:

<bindings>
  <ws2007HttpBinding>
    <binding name="stsBinding">
      <security mode="Message">
        <message clientCredentialType="UserName"
                 establishSecurityContext="false"
                 negotiateServiceCredential="true"/>
      </security>
    </binding>
  </ws2007HttpBinding>
</bindings>

As you can see, the same security characteristics that were previously discussed for other WCF services also apply for a STS implemented with WIF.

The last step in the basic implementation of a STS is to host the service so it becomes available to be consumed by different client applications. A new service host type, Microsoft.IdentityModel.Protocols.WSTrust.WSTrustServiceHost, is provided for that purpose.

One of the constructors for this new service host receives as arguments the configuration for the STS, and the base addresses where the service will listen for incoming requests:

static void Main( string[] args )
{
    X509Certificate2 stsCertificate = CertificateUtil.GetCertificate(
        StoreName.My,
        StoreLocation.LocalMachine,
        "CN=WCFSTS");

    // Create and setup the configuration for our STS
    SigningCredentials signingCreds = new X509SigningCredentials(stsCertificate);
    SecurityTokenServiceConfiguration config =
        new SecurityTokenServiceConfiguration("http://WCFBookSTS", signingCreds);

    config.SecurityTokenHandlers.AddOrReplace(new CustomUsernameTokenHandler());
    // Set the STS implementation class type
    config.SecurityTokenService = typeof( MySecurityTokenService );

    // Create the WS-Trust service host with our STS configuration
    using ( WSTrustServiceHost host = new WSTrustServiceHost( config, new Uri(
     "http://localhost:6000" ) ) )
    {
        host.Open();
        Console.WriteLine( "WCFBookSTS started, press ENTER to stop ..." );
        Console.ReadLine();
        host.Close();
    }
}

SecurityAuthenticationFederatedProgram.cs

The previous code initializes the STS configuration with the signing credentials that the STS will use to sign the issued SAML tokens, configure a custom security handler (discussed next), and finally create and open the service host using the configuration and the base address (http://localhost:6000).

Security Token Handlers

A security token handler in WIF represents a new way to plug in custom token handling functionality. By writing a token handler, you can add functionality to serialize, deserialize, authenticate, and create a specific kind of token.

WIF has basically replaced the core functionality that WCF provides to parse security tokens, and validate them using credential validators such as the UserNamePasswordValidator or the X509CertificateValidator. This infrastructure is still layered on top of the WCF service layer, but most of the functionality that you would write to extends the WCF built-in security token. Authenticators will not work with this new architecture.

The base class for creating a new token handler is Microsoft.IdentityModel.Tokens.SecurityTokenHandler:

public abstract class SecurityTokenHandler
{

    public virtual bool CanReadToken(XmlReader reader);
    public virtual SecurityToken ReadToken(XmlReader reader);
    public virtual ClaimsIdentityCollection ValidateToken(SecurityToken token);

    public virtual void WriteToken(XmlWriter writer, SecurityToken token);
    public virtual bool CanValidateToken { get; }
    public virtual bool CanWriteToken { get; }

    public abstract Type TokenType { get; }
}

You can derive this class, implement the abstract methods, and override some of the available virtual methods to customize the handlings of an existing security token or support new ones.

WIF ships with a set of built-in token handlers for some of the well-known tokens. These include KerberosSecurityTokenHandler (for Kerberos tokens), UserNameSecurityTokenHandler (for username tokens), X509SecurityTokenHandler (for X509 certificate tokens), Saml11SecurityTokenHandler (for SAML 1.1 security tokens), or Saml2SecurityTokenHandler (for SAML 2.0 security tokens).

For example, if you want to customize the way username tokens are authenticated, you need to derive the class UsernameSecurityTokenHandler and provide the implementation of the ValidateToken method:

public class CustomUsernameTokenHandler : UserNameSecurityTokenHandler
{
    public override bool CanValidateToken
    {
        get
        {
            return true;
        }
    }

    public override Microsoft.IdentityModel.Claims.ClaimsIdentityCollection
     ValidateToken(System.IdentityModel.Tokens.SecurityToken token)
    {
        UserNameSecurityToken userNameToken = token as UserNameSecurityToken;
        if (userNameToken.UserName != "joe" ||
            userNameToken.Password != "bar")
        {
            throw new SecurityTokenValidationException(
               "The user can not be authenticated");
        }

        return new ClaimsIdentityCollection(new IClaimsIdentity[] {
            new ClaimsIdentity(
            new Claim(System.IdentityModel.Claims.ClaimTypes.Name,
userNameToken.UserName), "CustomUsernameTokenHandler")});

    }

    public override SecurityTokenHandler Clone()
    {
        return new CustomUsernameTokenHandler();
    }
}

In this example, the handler is validating that the username and password are equal to "Joe" and "bar" respectively. Also, that method needs to provide the list of claims available in the parsed token (the Name claim in this case).

The security token handlers can either be configured through configuration or code using the STS configuration class, as is shown here:

SecurityTokenServiceConfiguration config =
        new SecurityTokenServiceConfiguration("http://WCFBookSTS", signingCreds);

    config.SecurityTokenHandlers.AddOrReplace(new CustomUsernameTokenHandler());

The AddOrReplace method replaces the built-in UserNameSecurityTokenHandler, which by default is WindowsUserNameSecurityTokenHandler, and validates the tokens using Windows authentication.

Configuring Federated Authentication in WCF

WCF provides a federation binding to support scenarios in which the services expect a token issued by an STS, which is typically a SAML token (although it could be any other). WsFederationHttpBinding is the original binding shipped in the first WCF release to support the issue token negotiation with an STS, and Ws2007FederatedHttpBinding is a new version that supports the latest WS-* protocol versions.

In this section, you use the Ws2007FederationHttpBinding to configure a WCF service that requires a SAML 1.1 token issued by a custom STS built with WIF. See Listing 9-1.

Note

If you want to support federated authentication over a transport, different from http, you need to do it with a custom binding.

Example 9.1. Claim-Aware Service

public class EchoClaims : IEchoClaims
{
    public List<string> Echo()
    {
List<string> claims = new List<string>();

        IClaimsPrincipal principal = Thread.CurrentPrincipal as IClaimsPrincipal;

        foreach (IClaimsIdentity identity in principal.Identities)
        {
            foreach (Claim claim in identity.Claims)
            {
                claims.Add(string.Format("{0} - {1}",
                    claim.ClaimType, claim.Value));
            }
        }

        return claims;
    }

}

The main difference is that the WCF authorization context is no longer needed to get access to the authentication claims. Instead, the IClaimsPrincipal instance available in the Thread.CurrentPrincipal property can be used to enumerate different IClaimsIdentity instances and its associated claims (as far as you use the built-in support, only one identity will be available in the principal, the primary identity).

The WCF configuration for that service using the Ws2007FederationHttpBinding is shown in Listing 9-2.

Example 9.2. Service Configuration

<services>
      <service name="WCFBook.Samples.EchoClaims"
               behaviorConfiguration="echoClaimsBehavior">
        <endpoint address="EchoClaims"
                  contract="WCFBook.Samples.IEchoClaims"
                  binding="ws2007FederationHttpBinding"
                  bindingConfiguration="echoClaimsBinding"></endpoint>
        <endpoint address="mex"
               binding="mexHttpBinding"
               contract="IMetadataExchange" />
      </service>
    </services>
    <bindings>
      <ws2007FederationHttpBinding>
        <binding name="echoClaimsBinding" >
          <security mode="Message">
            <message negotiateServiceCredential="true">
              <claimTypeRequirements>
                <add claimType=
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
isOptional="false"/>
                <add claimType="http://WCFBookSamples/2008/05/AgeClaim"
isOptional="false"/>
</claimTypeRequirements>
            </message>
          </security>
        </binding>
      </ws2007FederationHttpBinding>
    </bindings>
    <behaviors>
      <serviceBehaviors>
        <behavior name="echoClaimsBehavior">
          <serviceMetadata httpGetEnabled="true"/>
          <serviceCredentials>
            <issuedTokenAuthentication
              certificateValidationMode="None"
              revocationMode="NoCheck">
              <knownCertificates>
                <add
                  findValue="CN=WCFSTS"
                  storeLocation="LocalMachine"
                  storeName="My"
                  x509FindType="FindBySubjectDistinguishedName"/>
              </knownCertificates>
            </issuedTokenAuthentication>
            <serviceCertificate
              findValue="CN=WCFServer"
              storeLocation="LocalMachine"
              storeName="My"
              x509FindType="FindBySubjectDistinguishedName"/>
          </serviceCredentials>

        </behavior>
      </serviceBehaviors>
    </behaviors>
  </system.serviceModel>

The service binding and behavior configuration has some settings worth pointing out. Inside the binding configuration, the <claimTypeRequirements> element indicates the custom claims expected by the service. These claims are exposed by WCF in the service WS-Policy, so the client knows what claims must be negotiated with the STS.

Inside the <serviceCredentials> behavior, the <issuedTokenAuthentication> element indicates which X509 signature should be used to validate the SAML token signature. This section only instructs WCF to validate the signature, and it is still the responsibility of the service to check whether the token has been issued by a trusted issuer (WIF provides an extensibility point for performing these checks).

The <serviceCertificate> element specifies the X509 certificate that needs to be used to decrypt the SAML token or the session key included within the token. (It is the same certificate provided in the GetScope method of the STS implementation example.)

As happens with most of the examples in this chapter, the X509 certificate validations have been disabled to use test certificates. These validations should be activated before rolling out the services to production.

The code for hosting the service and enabling claims-based security with WIF also requires some modifications with respect to the traditional way that you would normally use:

static void Main(string[] args)
{
    ServiceHost host = new ServiceHost(typeof(EchoClaims),
        new Uri("http://localhost:8000"));
    try
    {
        ServiceConfiguration configuration = new
ServiceConfiguration("WCFBook.Samples.EchoClaims");
        configuration.IssuerNameRegistry = new CustomIssuerNameRegistry();

        FederatedServiceCredentials.ConfigureServiceHost(host, configuration);

        host.Open();

        Console.WriteLine("Service running....");
        Console.WriteLine("Press a key to quit");
        Console.ReadKey();
    }
    finally
    {
        host.Close();
    }
}

The method call FederatedServiceCredentials.ConfigureServiceHost injects and initializes the WCF service host with all the extensions required by the WIF infrastructure — this also includes the built-in security token handlers.

The code is also setting a custom class in the configuration property IssuerNameRegistry. As discussed previously, this is the extensibility point for performing validations against the token issuer.

A simple implementation of the base class Microsoft.IdentityModel.Tokens.IssuerNameRegistry is shown in Listing 9-3.

Example 9.3. IssuerNameRegistry Implementation

public class CustomIssuerNameRegistry : IssuerNameRegistry
{
    public override string GetIssuerName(System.IdentityModel.Tokens.SecurityToken
    securityToken)
    {
        X509SecurityToken token = securityToken as X509SecurityToken;
        if (token == null)
        {
            throw new SecurityTokenException("Token is not a X509 Security Token");
        }

        if (token.Certificate.SubjectName.Name != "CN=WCFSTS")
{
            throw new SecurityTokenException("STS not supported");
        }


        return "WCFSTS";
    }
}

As you might notice, this implementation is quite simple. It validates that the token was signed by the X509 certificate with subject name "CN=WCFSTS" (only the trusted STS should have this certificate private key). You probably need to perform more complex validations for production-ready services, as only checking the subject name is not safe in most scenarios (anyone can generate a valid certificate with the same subject name).

Listing 9-4 shows the corresponding configuration for consuming this service on the client side.

Example 9.4. Client Configuration

<system.serviceModel>
      <client>
        <endpoint address="http://localhost:8000/EchoClaims"
                  binding="ws2007FederationHttpBinding"
                  bindingConfiguration="echoClaimsBinding"
                  contract="EchoClaimsReference.IEchoClaims"
                  name="WSHttpBinding_IEchoClaims"
                  behaviorConfiguration="echoClaimsBehavior">
          <identity>
            <dns value="WCFServer"/>
          </identity>
        </endpoint>
      </client>
      <bindings>
        <ws2007FederationHttpBinding>
          <binding name="echoClaimsBinding">
            <security mode="Message">
              <message>
                <claimTypeRequirements>
                  <add claimType=
"http://schemas.xmlsoap.org/ws/2005/05/identity/claims/name"
isOptional="false"/>
                  <add claimType="http://WCFBookSamples/2008/05/AgeClaim"
isOptional="false"/>
                </claimTypeRequirements>
                <issuer address="http://localhost:6000/WCFBookSTS"
                        bindingConfiguration="stsBinding"
                        binding="ws2007HttpBinding">
                  <identity>
                    <dns value="WCFSTS"/>
</identity>
                </issuer>
                <issuerMetadata
address="http://localhost:6000/WCFBookSTS/Mex"></issuerMetadata>
              </message>
            </security>
          </binding>
        </ws2007FederationHttpBinding>

        <ws2007HttpBinding>
            <binding name="stsBinding">
              <security mode="Message">
                <message clientCredentialType="UserName"
                  establishSecurityContext="false"
                  negotiateServiceCredential="true"/>
              </security>
            </binding>
        </ws2007HttpBinding>
      </bindings>

      <behaviors>
        <endpointBehaviors>
          <behavior name="echoClaimsBehavior">
            <clientCredentials>
              <serviceCertificate>
                <defaultCertificate
                  findValue="CN=WCFSTS"
                  storeLocation="LocalMachine"
                  storeName="My"
                  x509FindType="FindBySubjectDistinguishedName"/>
                <authentication
                  revocationMode="NoCheck"
                  certificateValidationMode="None"/>
              </serviceCertificate>
            </clientCredentials>
          </behavior>
        </endpointBehaviors>
      </behaviors>
    </system.serviceModel>

The main difference with a traditional WCF endpoint is that two bindings are used, one for specifying the communication details with the final service, Ws2007FederatedBinding, and another for doing the same with the STS, Ws2007HttpBinding.

As you might notice, Ws2007FederatedBinding replicates the same configuration that you set on the service side with an extra element <issuer>. This specifies the issuer address a reference to the binding used for setting the communication details.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.15.18.198