Enabling WIF ActAs via configuration

Identity delegation is one of the most common scenarios in Service Oriented (SO) enterprise infrastructures. Essentially,  this pattern applies to scenarios where the final recipient of the issued token can inspect the entire delegation chain and see not just the client, but all intermediaries to perform access control, auditing and other related activities based on the whole identity delegation chain.

The current version of Windows Identity Foundation (WIF) provides an elegant model for enabling identity delegation by using the WS-Trust ActAs attribute. My colleague and friend Pablo Cibraro posted a great explanation of the ActAs support in WIF.

In a nutshell, a client application can enable an ActAs token by invoking the CreateChannelActingAs operation of the WCF channel factory as illustrated in the following code.

   1: SecurityToken callerToken = null;
   2:  
   3: IClaimsPrincipal claimsPrincipal = Thread.CurrentPrincipal as IClaimsPrincipal;
   4: if (claimsPrincipal != null)
   5: {
   6:   foreach (IClaimsIdentity claimsIdentity in claimsPrincipal.Identities)
   7:   {
   8:     if (claimsIdentity.BootstrapToken is SamlSecurityToken)
   9:     {
  10:       callerToken = claimsIdentity.BootstrapToken;
  11:       break;
  12:      }
  13:    }
  14: }
  15:  
  16: channel = factory.CreateChannelActingAs<IMyService>(callerToken);

Despite of the simplicity of the previous approach, it still requires to include the code as part of the client application. However, there is a large number of scenarios on which we would like to abstract that complexity from the developer by enabling a declarative interface via configuration. The current version of WIF only supports enabling the use of the ActAs token programmatically. However after a few minutes of using .NET reflector we can figure out that the we can create an ActAs token using something similar to the following code.

   1: FederatedClientCredentialsParameters parameters = 
   2:                      new FederatedClientCredentialsParameters();
   3: parameters.ActAs = callerToken;
   4: ((IChannel)channel).GetProperty<ChannelParameterCollection>().Add(parameters);

Using the previous techniques, it is not hard to create a WCF client message inspector that attaches an ActAs token to the WS-Trust request as shown in the following code.

 

   1:  
   2: public class ActAsInterceptor : IClientMessageInspector
   3: {
   4:   public void AfterReceiveReply(ref Message reply, object correlationState)
   5:   { 
   6:   }
   7:  
   8:   public object BeforeSendRequest(ref Message request, IClientChannel channel)
   9:   {
  10:     SecurityToken callerToken = null;
  11:    IClaimsPrincipal claimsPrincipal = Thread.CurrentPrincipal as IClaimsPrincipal;
  12:    if (claimsPrincipal != null)
  13:    {
  14:      foreach (IClaimsIdentity claimsIdentity in claimsPrincipal.Identities)
  15:      {
  16:        if (claimsIdentity.BootstrapToken is SamlSecurityToken)
  17:        {
  18:          callerToken = claimsIdentity.BootstrapToken;
  19:          break;
  20:        }
  21:      }
  22:    }
  23:    FederatedClientCredentialsParameters parameters = 
  24:                                       new FederatedClientCredentialsParameters();
  25:    if (null != callerToken)
  26:    {
  27:      parameters.ActAs = callerToken;
  28:    try
  29:    {
  30:    ((IChannel)channel).GetProperty<ChannelParameterCollection>().Add(parameters);
  31:    }
  32:      catch (Exception ex)
  33:      {}
  34:    }
  35:    return null;
  36:   }
  37:  
  38:  }
  39: }

We can inject our inspector into the WCF client runtime by creating an endpoint behavior as illustrated in the following code.

   1: public class FederatedIdentityExtensionsBehavior: IEndpointBehavior
   2:  {
   3:  
   4:      public void AddBindingParameters(ServiceEndpoint endpoint, 
   5:                                    BindingParameterCollection bindingParameters)
   6:      {
   7:      
   8:      }
   9:  
  10:      public void ApplyClientBehavior(ServiceEndpoint endpoint,
  11:                                      ClientRuntime clientRuntime)
  12:      {
  13:          clientRuntime.MessageInspectors.Add(new ActAsInterceptor());
  14:      }
  15:  
  16:      public void ApplyDispatchBehavior(ServiceEndpoint endpoint, 
  17:                                       EndpointDispatcher endpointDispatcher)
  18:      {
  19:  
  20:      }
  21:  
  22:      public void Validate(ServiceEndpoint endpoint)
  23:      {
  24:          FederatedClientCredentials item = null;
  25:          ClientCredentials other = endpoint.Behaviors.Find<ClientCredentials>();
  26:          if (other != null)
  27:          {
  28:              endpoint.Behaviors.Remove(other.GetType());
  29:              item = new FederatedClientCredentials(other);
  30:  
  31:              endpoint.Behaviors.Add(item);
  32:          }
  33:      }
  34:  }

After this we can enable our behavior via configuration by implementing a behavior extension element as shown below.

   1: public class FederatedIdentityExtensionsBehaviorElement: BehaviorExtensionElement
   2:     {
   3:         public override Type BehaviorType
   4:         {
   5:             get { return typeof(FederatedIdentityExtensionsBehavior); }
   6:         }
   7:  
   8:         protected override object CreateBehavior()
   9:         {
  10:             return new FederatedIdentityExtensionsBehavior();
  11:         }
  12:     }

After this, our client application can take advantage of the ActAs functionality by configuring our endpoint behavior as illustrated below.

   1: <client>
   2:   <endpoint address="service endpoint"
   3:     contract="ISampleService" 
   4:     binding="customBinding" 
   5:     bindingConfiguration="FederatedBinding" 
   6:     behaviorConfiguration="FederatedBehavior" 
   7:     name="SampleService">
   8:   </endpoint>
   9: </client>
  10:  
  11: <bindings>
  12:   <customBinding>
  13:     <binding name="FederatedBinding">
  14:     <security authenticationMode="IssuedTokenForCertificate" 
  15:       messageSecurityVersion=
  16:       "WSSecurity11WSTrust13...">
  17:       <issuedTokenParameters keyType="SymmetricKey"

18: tokenType=

"http://docs.oasis-open.org/wss/oasis-wss-saml-token-profile-1.1#SAMLV1.1">

  19:       <issuer address="STS URL…." 
  20:       binding="customBinding" bindingConfiguration="stsBinding">
  21:       </issuer>
  22:   
  23:       <issuerMetadata address="STS MEX URL...."/>
  24:       </issuedTokenParameters>
  25:     </security>
  26:     <textMessageEncoding messageVersion="Soap12WSAddressing10"/>
  27:     <httpTransport/>
  28: </binding>
  29: <binding name="stsBinding">
  30: <security authenticationMode="MutualCertificate"
  31:   messageSecurityVersion="WSSecurity11WSTrust13...">
  32:             
  33:  </security>
  34: <textMessageEncoding messageVersion="Soap12WSAddressing10"/>
  35: <httpTransport/>
  36: </binding>
  37: </customBinding>
  38: </bindings>
  39:  
  40: <behaviors>
  41:   <endpointBehaviors>
  42:     <behavior name="FederatedBehavior">
  43:     <clientCredentials>
  44:       Client credentials section...
  45:     </clientCredentials>
  46:  
  47:    <federatedActAs />
  48:     
  49:  </behavior>
  50:  </endpointBehaviors>
  51: </behaviors>
  52: </system.serviceModel>

Using the previous technique, client applications can enable identity delegation scenarios using a 100% declarative approach.

1 Comment

  • The example will work with following changes:

    - Raplace FederatedIdentityExtensionsBehavior with:

    public class FederatedIdentityExtensionsBehavior : IEndpointBehavior
    {
    public void AddBindingParameters(ServiceEndpoint endpoint, BindingParameterCollection bindingParameters)
    {
    }

    public void ApplyClientBehavior(ServiceEndpoint endpoint, ClientRuntime clientRuntime)
    {
    clientRuntime.ChannelInitializers.Add(new ActAsChannelInitializer());
    }

    public void ApplyDispatchBehavior(ServiceEndpoint endpoint, EndpointDispatcher endpointDispatcher)
    {
    }

    public void Validate(ServiceEndpoint endpoint)
    {
    FederatedClientCredentials item = null;
    ClientCredentials other = endpoint.Behaviors.Find();
    if (other != null)
    {
    endpoint.Behaviors.Remove(other.GetType());
    item = new FederatedClientCredentials(other);
    endpoint.Behaviors.Add(item);
    }
    }
    }

    - Delete the ActAsInterceptor

    - Add an ActAsChannelInitializer

    public class ActAsChannelInitializer : IChannelInitializer
    {
    #region IChannelInitializer Members

    public void Initialize(System.ServiceModel.IClientChannel channel)
    {
    SecurityToken callerToken = null;
    IClaimsPrincipal claimsPrincipal = Thread.CurrentPrincipal as IClaimsPrincipal;
    if (claimsPrincipal != null)
    {
    foreach (IClaimsIdentity claimsIdentity in claimsPrincipal.Identities)
    {
    if (claimsIdentity.BootstrapToken is SamlSecurityToken)
    {
    callerToken = claimsIdentity.BootstrapToken;
    break;
    }
    }
    }
    FederatedClientCredentialsParameters parameters = new FederatedClientCredentialsParameters();
    if (null != callerToken)
    {
    parameters.ActAs = callerToken;
    try
    {
    channel.GetProperty().Add(parameters);
    }
    catch (Exception ex)
    { }
    }
    }

    #endregion
    }


Comments have been disabled for this content.